using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel.Syndication;
using System.Web;
using System.Web.Mvc;
using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Domain;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Localization;
using Nop.Core.Domain.Logistics;
using Nop.Core.Domain.Media;
using Nop.Core.Domain.Orders;
using Nop.Services.Catalog;
using Nop.Services.Common;
using Nop.Services.Customers;
using Nop.Services.Directory;
using Nop.Services.Events;
using Nop.Services.Helpers;
using Nop.Services.Localization;
using Nop.Services.Logging;
using Nop.Services.Media;
using Nop.Services.Messages;
using Nop.Services.Orders;
using Nop.Services.Security;
using Nop.Services.Seo;
using Nop.Services.Stores;
using Nop.Services.Tax;
using Nop.Web.Extensions;
using Nop.Web.Framework;
using Nop.Web.Framework.Controllers;
using Nop.Web.Framework.Events;
using Nop.Web.Framework.Security;
using Nop.Web.Framework.UI.Captcha;
using Nop.Web.Infrastructure.Cache;
using Nop.Web.Models.Catalog;
using Nop.Web.Models.Media;
using Nop.Web.Models.ShoppingCart;
using Nop.Core.Domain.Discounts;
using Nop.Core.Domain.Shipping;
using Nop.Services.Payments;
using Nop.Core.Domain.Tax;
using Nop.Web.Models;
using Nop.Services.Discounts;
using Nop.Services.Topics;

namespace Nop.Web.Controllers
{
    [MobileRedirect]
    public partial class CatalogController : BaseNopController
    {
        #region Fields

        private readonly ICategoryService _categoryService;
        private readonly IManufacturerService _manufacturerService;
        private readonly IProductService _productService;
        private readonly IProductTemplateService _productTemplateService;
        private readonly ICategoryTemplateService _categoryTemplateService;
        private readonly IManufacturerTemplateService _manufacturerTemplateService;
        private readonly IProductAttributeService _productAttributeService;
        private readonly IProductAttributeParser _productAttributeParser;
        private readonly IWorkContext _workContext;
        private readonly ITaxService _taxService;
        private readonly ICurrencyService _currencyService;
        private readonly IPictureService _pictureService;
        private readonly ILocalizationService _localizationService;
        private readonly IPriceCalculationService _priceCalculationService;
        private readonly IPriceFormatter _priceFormatter;
        private readonly IWebHelper _webHelper;
        private readonly IOrderService _orderService;
        private readonly ISpecificationAttributeService _specificationAttributeService;
        private readonly ICustomerContentService _customerContentService;
        private readonly ICustomerService _customerService;
        private readonly IDateTimeHelper _dateTimeHelper;
        private readonly IShoppingCartService _shoppingCartService;
        private readonly IRecentlyViewedProductsService _recentlyViewedProductsService;
        private readonly ICompareProductsService _compareProductsService;
        private readonly IWorkflowMessageService _workflowMessageService;
        private readonly IProductTagService _productTagService;
        private readonly IOrderReportService _orderReportService;
        private readonly IGenericAttributeService _genericAttributeService;
        private readonly IBackInStockSubscriptionService _backInStockSubscriptionService;
        private readonly IAclService _aclService;
        private readonly IPermissionService _permissionService;
        private readonly IDownloadService _downloadService;
        private readonly ICustomerActivityService _customerActivityService;
        private readonly IEventPublisher _eventPublisher;
        private readonly IStoreService _storeService;
        private readonly IStoreTemplateService _storeTemplateService;
        private readonly IOrderTotalCalculationService _orderTotalCalculationService;
        private readonly IPaymentService _paymentService;
        private readonly ITopicService _topicService;
        private readonly HttpContextBase _httpContext;
        private readonly IRegionService _regionService;
        private readonly MediaSettings _mediaSettings;
        private readonly CatalogSettings _catalogSettings;
        private readonly ShoppingCartSettings _shoppingCartSettings;
        private readonly StoreInformationSettings _storeInformationSettings;
        private readonly LocalizationSettings _localizationSettings;
        private readonly CustomerSettings _customerSettings;
        private readonly ICacheManager _cacheManager;
        private readonly IDiscountService _discountService;
        private readonly CaptchaSettings _captchaSettings;
        private readonly TaxSettings _taxSettings;
        private readonly LogisticsInformationSettings _logisticsInformationSettings;
        private readonly ILogger _logger;
        #endregion

        #region Constructors

        public CatalogController(ICategoryService categoryService,
            IManufacturerService manufacturerService, IProductService productService,
            IProductTemplateService productTemplateService,
            ICategoryTemplateService categoryTemplateService,
            IManufacturerTemplateService manufacturerTemplateService,
            IProductAttributeService productAttributeService, IProductAttributeParser productAttributeParser,
            IWorkContext workContext, ITaxService taxService, ICurrencyService currencyService,
            IPictureService pictureService, ILocalizationService localizationService,
            IPriceCalculationService priceCalculationService, IPriceFormatter priceFormatter,
            IWebHelper webHelper, ISpecificationAttributeService specificationAttributeService,
            ICustomerContentService customerContentService, IDateTimeHelper dateTimeHelper,
            IShoppingCartService shoppingCartService, IRegionService regionService,
            IRecentlyViewedProductsService recentlyViewedProductsService, ICompareProductsService compareProductsService,
            IWorkflowMessageService workflowMessageService, IProductTagService productTagService,
            IOrderReportService orderReportService, IGenericAttributeService genericAttributeService,
            IBackInStockSubscriptionService backInStockSubscriptionService, IAclService aclService,
            IPermissionService permissionService, IDownloadService downloadService,
            ICustomerActivityService customerActivityService, IEventPublisher eventPublisher,
            IStoreService storeService, ICustomerService customerService,
            IStoreTemplateService storeTemplateService,
            HttpContextBase httpContext, IOrderService orderService,
            MediaSettings mediaSettings, CatalogSettings catalogSettings,
            ShoppingCartSettings shoppingCartSettings, StoreInformationSettings storeInformationSettings,
            LocalizationSettings localizationSettings, CustomerSettings customerSettings,
            LogisticsInformationSettings logisticsInformationSettings,
            CaptchaSettings captchaSettings,
            IOrderTotalCalculationService orderTotalCalculationService,
            IPaymentService paymentService, TaxSettings taxSettings,
            ICacheManager cacheManager, IDiscountService discountService, ITopicService topicService, ILogger logger)
        {
            this._categoryService = categoryService;
            this._manufacturerService = manufacturerService;
            this._productService = productService;
            this._productTemplateService = productTemplateService;
            this._categoryTemplateService = categoryTemplateService;
            this._manufacturerTemplateService = manufacturerTemplateService;
            this._productAttributeService = productAttributeService;
            this._productAttributeParser = productAttributeParser;
            this._workContext = workContext;
            this._taxService = taxService;
            this._currencyService = currencyService;
            this._pictureService = pictureService;
            this._localizationService = localizationService;
            this._priceCalculationService = priceCalculationService;
            this._priceFormatter = priceFormatter;
            this._webHelper = webHelper;
            this._orderService = orderService;
            this._specificationAttributeService = specificationAttributeService;
            this._customerContentService = customerContentService;
            this._customerService = customerService;
            this._dateTimeHelper = dateTimeHelper;
            this._shoppingCartService = shoppingCartService;
            this._recentlyViewedProductsService = recentlyViewedProductsService;
            this._compareProductsService = compareProductsService;
            this._workflowMessageService = workflowMessageService;
            this._productTagService = productTagService;
            this._orderReportService = orderReportService;
            this._genericAttributeService = genericAttributeService;
            this._backInStockSubscriptionService = backInStockSubscriptionService;
            this._aclService = aclService;
            this._permissionService = permissionService;
            this._downloadService = downloadService;
            this._customerActivityService = customerActivityService;
            this._eventPublisher = eventPublisher;
            this._storeService = storeService;
            this._storeTemplateService = storeTemplateService;
            this._httpContext = httpContext;
            this._paymentService = paymentService;
            this._mediaSettings = mediaSettings;
            this._catalogSettings = catalogSettings;
            this._shoppingCartSettings = shoppingCartSettings;
            this._storeInformationSettings = storeInformationSettings;
            this._localizationSettings = localizationSettings;
            this._customerSettings = customerSettings;
            this._captchaSettings = captchaSettings;
            this._logisticsInformationSettings = logisticsInformationSettings;
            this._cacheManager = cacheManager;
            this._orderTotalCalculationService = orderTotalCalculationService;
            this._taxSettings = taxSettings;
            this._discountService = discountService;
            this._regionService = regionService;
            this._topicService = topicService;
            this._logger = logger;
        }

        #endregion

        #region Utilities

        [NonAction]
        protected int GetRegionId()
        {
            var region = _workContext.CurrentCustomer.Region;
            int regionId = region == null ? 0 : region.Id;

            return regionId;
        }

        [NonAction]
        protected List<int> GetChildCategoryIds(int parentCategoryId, bool showHidden = false)
        {
            var customerRolesIds = _workContext.CurrentCustomer.CustomerRoleMappings
                .Where(cr => cr.CustomerRole.Active).Select(cr => cr.Id).ToList();
            string cacheKey = string.Format(ModelCacheEventConsumer.CATEGORY_CHILD_IDENTIFIERS_MODEL_KEY, parentCategoryId, showHidden, string.Join(",", customerRolesIds));
            return _cacheManager.Get(cacheKey, () =>
            {
                var categoriesIds = new List<int>();
                var categories = _categoryService.GetAllCategoriesByParentCategoryId(parentCategoryId, showHidden);
                foreach (var category in categories)
                {
                    categoriesIds.Add(category.Id);
                    categoriesIds.AddRange(GetChildCategoryIds(category.Id, showHidden));
                }
                return categoriesIds;
            });
        }

        [NonAction]
        protected IList<Category> GetCategoryBreadCrumb(Category category)
        {
            if (category == null)
                throw new ArgumentNullException("category");

            var breadCrumb = new List<Category>();

            while (category != null && //category is not null
                !category.Deleted && //category is not deleted
                category.Published && //category is published
                _aclService.Authorize(category)) //ACL
            {
                breadCrumb.Add(category);
                category = _categoryService.GetCategoryById(category.ParentCategoryId);
            }
            breadCrumb.Reverse();
            return breadCrumb;
        }

        [NonAction]
        protected IList<CategoryNavigationModel.CategoryModel> PrepareCategoryNavigationModel(IList<Category> breadCrumb,
            int rootCategoryId, int level)
        {
            var result = new List<CategoryNavigationModel.CategoryModel>();
            foreach (var category in _categoryService.GetAllCategoriesByParentCategoryId(rootCategoryId))
            {
                var categoryModel = new CategoryNavigationModel.CategoryModel()
                {
                    Id = category.Id,
                    Name = category.GetLocalized(x => x.Name),
                    SeName = category.GetSeName(),
                    NumberOfParentCategories = level
                };

                //show product number for each category
                if (_catalogSettings.ShowCategoryProductNumber)
                {
                    var categoryIds = new List<int>();
                    categoryIds.Add(category.Id);
                    //include subcategories
                    if (_catalogSettings.ShowCategoryProductNumberIncludingSubcategories)
                        categoryIds.AddRange(GetChildCategoryIds(category.Id));
                    IList<int> filterableSpecificationAttributeOptionIds = null;
                    categoryModel.NumberOfProducts = _productService.SearchProducts(GetRegionId(), null, categoryIds,
                        0, null, null, null, 0, string.Empty, false, false, 0, null,
                        ProductSortingEnum.Position, 0, 1,
                        false, out filterableSpecificationAttributeOptionIds).TotalCount;
                }

                //subcategories
                for (int i = 0; i <= breadCrumb.Count - 1; i++)
                    if (breadCrumb[i].Id == category.Id)
                        categoryModel.SubCategories.AddRange(PrepareCategoryNavigationModel(breadCrumb, category.Id, level + 1));

                result.Add(categoryModel);
            }

            return result;
        }


        [NonAction]
        protected IList<CategoryNavigationModel.CategoryModel> PrepareCategoryNavigationFrontModel(IList<Category> breadCrumb,
            int rootCategoryId, int level, int storeId)
        {
            var result = new List<CategoryNavigationModel.CategoryModel>();
            foreach (var category in _categoryService.GetAllCategoriesByParentCategoryId(rootCategoryId, storeId: storeId))
            {
                var categoryModel = new CategoryNavigationModel.CategoryModel()
                {
                    Id = category.Id,
                    Name = category.GetLocalized(x => x.Name),
                    SeName = category.GetSeName(),
                    NumberOfParentCategories = level
                };
                var SubCategoryCount = _categoryService.GetAllCategoriesByParentCategoryId(category.Id);
                if (SubCategoryCount.Count > 0)
                {
                    var subcategoryresult = new List<CategoryNavigationModel.CategoryModel>();
                    foreach (var subcategory in _categoryService.GetAllCategoriesByParentCategoryId(category.Id))
                    {
                        var subcategoryModel = new CategoryNavigationModel.CategoryModel()
                        {
                            Id = subcategory.Id,
                            Name = subcategory.GetLocalized(x => x.Name),
                            SeName = subcategory.GetSeName(),
                            NumberOfParentCategories = level
                        };
                        subcategoryresult.Add(subcategoryModel);
                    }
                    categoryModel.SubCategories.AddRange(subcategoryresult);
                }
                result.Add(categoryModel);
            }

            return result;
        }

        [NonAction]
        protected HomePageProductsModel PreparePagedProducts(IList<Product> products, int? pagenumber, int? productThumbPictureSize)
        {
            int pageIndex = pagenumber.HasValue && pagenumber.Value > 0 ? pagenumber.Value - 1 : 0;
            //TODO: need to test. 
            var paged = new PagedList<Product>(
                products,
                pageIndex,
                _catalogSettings.HomepageProductsPerPage == 0 ? 12 : _catalogSettings.HomepageProductsPerPage);

            var model = new HomePageProductsModel()
            {
                Products = PrepareProductOverviewModels(paged, productThumbPictureSize: productThumbPictureSize).ToList()
            };
            model.PageModel.LoadPagedList(paged);
            model.IsLogin = !_workContext.CurrentCustomer.IsGuest();

            return model;
        }

        [NonAction]
        protected IEnumerable<ProductOverviewModel> PrepareProductOverviewModels(IEnumerable<Product> products,
            bool preparePriceModel = true, bool preparePictureModel = true,
            bool prepareStoreModel = true, bool prepareReviewModel = true,
            int? productThumbPictureSize = null, bool prepareSpecificationAttributes = false,
            bool forceRedirectionAfterAddingToCart = false)
        {
            if (products == null)
                throw new ArgumentNullException("products");
            var time = StackExchange.Profiling.MiniProfiler.Current==null? DateTime.Now.Millisecond:StackExchange.Profiling.MiniProfiler.Current.DurationMilliseconds;
            _logger.Debug("PrepareProductOverviewModels Start Elapsed Time:" + time);
            //performance optimization. let's load all variants at one go
            var allVariants = _productService.GetProductVariantsByProductIds(products.Select(x => x.Id).ToArray(),
                                                                             GetRegionId());

            var models = new List<ProductOverviewModel>();
            foreach (var product in products)
            {
                var model = new ProductOverviewModel()
                {
                    Id = product.Id,
                    Name = product.GetLocalized(x => x.Name),
                    ShortDescription = product.GetLocalized(x => x.ShortDescription),
                    FullDescription = product.GetLocalized(x => x.FullDescription),
                    SeName = product.GetSeName()

                };

                //product review / rating
                if (prepareReviewModel && product.ApprovedTotalReviews > 0)
                {
                    #region Prepare review model

                    double average = Math.Truncate(((float)product.ApprovedRatingSum / product.ApprovedTotalReviews) * 5) / 5;

                    float avg = product.ApprovedRatingSum / product.ApprovedTotalReviews;
                    model.ProductReview = new ProductOverviewModel.ProductReviewModel()
                    {
                        AllowCustomerReviews = product.AllowCustomerReviews,
                        RatingAverage = average,
                        TotalReviews = product.ApprovedTotalReviews,
                        RatingSum = product.ApprovedRatingSum
                    };

                    model.ProductReview.SymbolValues = new int[Rating.MaxRating];
                    int completed = (int)avg;
                    int partial = ((int)(avg * 10f)) % 10;

                    for (int i = 0; i < model.ProductReview.SymbolValues.Length; i++)
                    {
                        if (i == completed)
                        {
                            model.ProductReview.SymbolValues[i] = partial;
                        }
                        else if (i < completed)
                        {
                            model.ProductReview.SymbolValues[i] = 10;
                        }
                    }

                    #endregion
                }

                //store 
                if (prepareStoreModel)
                {
                    #region Prepare store model

                    model.ProductStore = new ProductOverviewModel.ProductStoreModel()
                    {
                        Id = product.Store.Id,
                        Name = product.Store.GetLocalized(x => x.Name),
                        SeName = product.Store.GetSeName(),
                        IsActive = !product.Store.Deleted && product.Store.Published,
                    };

                    var defaultProductStorePictureCacheKey = string.Format(ModelCacheEventConsumer.STORE_PICTURE_MODEL_KEY, product.StoreId, _mediaSettings.StoreLogoProductBoxPictureSize, true, _workContext.WorkingLanguage.Id, _webHelper.IsCurrentConnectionSecured());
                    model.ProductStore.StorePictureModel = _cacheManager.Get(defaultProductStorePictureCacheKey, () =>
                    {
                        var productStore = product.Store;
                        var picture = _pictureService.GetPictureById(productStore.PictureId);
                        var pictureModel = new PictureModel()
                        {
                            ImageUrl = _pictureService.GetPictureUrl(picture, _mediaSettings.StoreLogoProductBoxPictureSize),
                            FullSizeImageUrl = _pictureService.GetPictureUrl(picture),
                            Title = string.Format(_localizationService.GetResource("Media.Product.ImageLinkTitleFormat"), model.Name),
                            AlternateText = string.Format(_localizationService.GetResource("Media.Product.ImageAlternateTextFormat"), model.Name)
                        };
                        return pictureModel;
                    });

                    #endregion
                }

                //price
                if (preparePriceModel)
                {
                    #region Prepare product price

                    var priceModel = new ProductOverviewModel.ProductPriceModel();


                    //var productVariants = _productService.GetProductVariantsByProductId(product.Id);
                    //we use already loaded variants
                    var productVariants = allVariants.Where(x => x.ProductId == product.Id && !x.IsOpenBoxProductVariant).ToList();
                    var openBoxProductVariants = allVariants.Where(x => x.ProductId == product.Id && x.IsOpenBoxProductVariant && x.StockQuantity > 0).ToList();
                    model.ProductVariantCount = productVariants.Count;
                    model.OutOfStock = productVariants.Sum(pv => pv.StockQuantity) == 0;
                    switch (productVariants.Count)
                    {
                        case 0:
                            {
                                //no variants
                                priceModel.OldPrice = null;
                                priceModel.OpenBoxPrice = null;
                                priceModel.Price = null;
                            }
                            break;
                        default:
                            {
                                if (_permissionService.Authorize(StandardPermissionProvider.DisplayPrices))
                                {
                                    //calculate for the maximum quantity (in case if we have tier prices)
                                    decimal? minimalPrice = null;
                                    decimal? minimalOpenBoxPrice = null;
                                    var productVariant = _priceCalculationService.GetProductVariantWithMinimalPrice(productVariants, _workContext.CurrentCustomer, true, int.MaxValue, out minimalPrice);
                                    var openBoxProductVariant = _priceCalculationService.GetProductVariantWithMinimalPrice(openBoxProductVariants, _workContext.CurrentCustomer, true, int.MaxValue, out minimalOpenBoxPrice);
                                    if (!productVariant.CustomerEntersPrice)
                                    {
                                        if (productVariant.CallForPrice)
                                        {
                                            priceModel.OldPrice = null;
                                            priceModel.Price = _localizationService.GetResource("Products.CallForPrice");
                                        }
                                        else if (minimalPrice.HasValue || minimalOpenBoxPrice.HasValue)
                                        {
                                            // fill open box product price only if non-open box product price is unavailable
                                            if (!minimalPrice.HasValue)
                                                minimalPrice = minimalOpenBoxPrice;

                                            //calculate prices
                                            List<TaxSurcharge> taxRate;
                                            decimal oldPriceBase = _taxService.GetProductPrice(productVariant, productVariant.OldPrice, out taxRate);
                                            decimal priceBase = _taxService.GetProductPrice(productVariant, productVariant.Price, out taxRate);
                                            decimal? openBoxPriceBase = null;
                                            if (minimalOpenBoxPrice.HasValue)
                                                openBoxPriceBase = _taxService.GetProductPrice(openBoxProductVariant, minimalOpenBoxPrice.Value, out taxRate);

                                            decimal oldPrice = _currencyService.ConvertFromPrimaryStoreCurrency(oldPriceBase, _workContext.WorkingCurrency);
                                            decimal price = _currencyService.ConvertFromPrimaryStoreCurrency(priceBase, _workContext.WorkingCurrency);
                                            decimal? openBoxPrice = null;
                                            if (minimalOpenBoxPrice.HasValue)
                                                openBoxPrice = _currencyService.ConvertFromPrimaryStoreCurrency(openBoxPriceBase.Value, _workContext.WorkingCurrency);

                                            //do we have tier prices configured?
                                            var tierPrices = new List<TierPrice>();
                                            if (productVariant.HasTierPrices)
                                            {
                                                tierPrices.AddRange(productVariant.TierPrices
                                                    .OrderBy(tp => tp.Quantity)
                                                    .ToList()
                                                    .RemoveDuplicatedQuantities());
                                            }
                                            bool displayFromMessage =
                                                //When there is just one tier (with  qty 1), there are no actual savings in the list.
                                                (tierPrices.Count > 0 && !(tierPrices.Count == 1 && tierPrices[0].Quantity <= 1)) ||
                                                //we have more than one variant
                                                (productVariants.Count > 1);
                                            if (displayFromMessage)
                                            {
                                                priceModel.OldPrice = null;
                                                priceModel.Price = String.Format(_localizationService.GetResource("Products.PriceRangeFrom"), _priceFormatter.FormatPrice(price));
                                            }
                                            else
                                            {
                                                if (priceBase != oldPriceBase && oldPriceBase != decimal.Zero)
                                                {
                                                    priceModel.OldPrice = _priceFormatter.FormatPrice(oldPrice);
                                                    priceModel.Price = _priceFormatter.FormatPrice(price);
                                                }
                                                else
                                                {
                                                    priceModel.OldPrice = null;
                                                    priceModel.Price = _priceFormatter.FormatPrice(price);
                                                }
                                            }
                                            if (productVariants.Count >= 1)
                                            {
                                                if (tierPrices.Count >= 1)
                                                {
                                                    priceModel.MemberTierPrice = _priceFormatter.FormatPrice(tierPrices.First().Price);

                                                    priceModel.SavePrice = _priceFormatter.FormatPrice(Math.Abs(tierPrices.First().Price - price));
                                                    priceModel.SavePercentage = Math.Abs(Math.Floor((tierPrices.First().Price - price) / (Math.Max(tierPrices.First().Price, price)) * 100)).ToString() + "%";
                                                }
                                                priceModel.IsMembeTier = tierPrices.FilterForCustomer(_workContext.CurrentCustomer).Count > 0;
                                            }
                                            if (openBoxPrice.HasValue)
                                            {
                                                bool displayOpenBoxFromMessage = openBoxProductVariants.Count > 1;
                                                if (displayOpenBoxFromMessage)
                                                    priceModel.OpenBoxPrice = String.Format(_localizationService.GetResource("Products.PriceRangeFrom"), _priceFormatter.FormatPrice(openBoxPrice.Value));
                                                else
                                                    priceModel.OpenBoxPrice = _priceFormatter.FormatPrice(openBoxPrice.Value);
                                            }
                                        }
                                        else
                                        {
                                            //Actually it's not possible (we presume that minimalPrice always has a value)
                                            //We never should get here
                                            Debug.WriteLine(string.Format("Cannot calculate minPrice for product variant #{0}", productVariant.Id));
                                        }
                                    }
                                }
                                else
                                {
                                    //hide prices
                                    priceModel.OldPrice = null;
                                    priceModel.OpenBoxPrice = null;
                                    priceModel.Price = null;
                                }
                            }
                            break;
                    }

                    //'add to cart' button
                    switch (productVariants.Count)
                    {
                        case 0:
                            {
                                // no variants
                                priceModel.DisableBuyButton = true;
                                priceModel.AvailableForPreOrder = false;
                                priceModel.AttributesRequiredBeforeAddingToCart = false;
                            }
                            break;
                        case 1:
                            {

                                //only one variant
                                var productVariant = productVariants[0];
                                priceModel.DisableBuyButton = productVariant.DisableBuyButton || !_permissionService.Authorize(StandardPermissionProvider.EnableShoppingCart);
                                if (!_permissionService.Authorize(StandardPermissionProvider.DisplayPrices))
                                {
                                    priceModel.DisableBuyButton = true;
                                }
                                priceModel.AvailableForPreOrder = productVariant.AvailableForPreOrder;
                                priceModel.AttributesRequiredBeforeAddingToCart = false;
                                if (productVariant.ProductVariantAttributes.Count > 0)
                                {
                                    foreach (var pva in productVariant.ProductVariantAttributes)
                                    {
                                        if (pva.IsRequired)
                                        {
                                            priceModel.AttributesRequiredBeforeAddingToCart = true;
                                        }
                                    }
                                }
                            }
                            break;
                        default:
                            {
                                //multiple variants
                                priceModel.DisableBuyButton = true;
                                priceModel.AvailableForPreOrder = false;
                                priceModel.AttributesRequiredBeforeAddingToCart = false;
                            }
                            break;
                    }

                    priceModel.ForceRedirectionAfterAddingToCart = forceRedirectionAfterAddingToCart;
                    model.ProductPrice = priceModel;
                    #endregion
                }

                //picture
                if (preparePictureModel)
                {
                    #region Prepare product picture

                    //If a size has been set in the view, we use it in priority
                    int pictureSize = productThumbPictureSize.HasValue ? productThumbPictureSize.Value : _mediaSettings.ProductDetailsPictureSize;
                    //prepare picture model
                    var defaultProductPictureCacheKey = string.Format(ModelCacheEventConsumer.PRODUCT_DEFAULTPICTURE_MODEL_KEY, product.Id, pictureSize, true, _workContext.WorkingLanguage.Id, _webHelper.IsCurrentConnectionSecured());
                    model.DefaultPictureModel = _cacheManager.Get(defaultProductPictureCacheKey, () =>
                    {
                        var picture = product.GetDefaultProductPicture(_pictureService);
                        var pictureModel = new PictureModel()
                        {
                            ImageUrl = _pictureService.GetPictureUrl(picture, pictureSize),
                            FullSizeImageUrl = _pictureService.GetPictureUrl(picture),
                            Title = string.Format(_localizationService.GetResource("Media.Product.ImageLinkTitleFormat"), model.Name),
                            AlternateText = string.Format(_localizationService.GetResource("Media.Product.ImageAlternateTextFormat"), model.Name)
                        };
                        return pictureModel;
                    });

                    #endregion
                }

                //specs
                if (prepareSpecificationAttributes)
                {
                    //specs for comparing
                    model.SpecificationAttributeModels = PrepareProductSpecificationModel(product);
                }

                models.Add(model);
            }
            _logger.Debug("PrepareProductOverviewModels End Elapsed Time:" + time);
            return models;
        }

        [NonAction]
        protected IList<ProductSpecificationModel> PrepareProductSpecificationModel(Product product)
        {
            if (product == null)
                throw new ArgumentNullException("product");

            string cacheKey = string.Format(ModelCacheEventConsumer.PRODUCT_SPECS_MODEL_KEY, product.Id, _workContext.WorkingLanguage.Id);
            return _cacheManager.Get(cacheKey, () =>
            {
                var model = _specificationAttributeService.GetProductSpecificationAttributesByProductId(product.Id, null, true)
                   .Select(psa =>
                   {
                       return new ProductSpecificationModel()
                       {
                           SpecificationAttributeId = psa.SpecificationAttributeOption.SpecificationAttributeId,
                           SpecificationAttributeName = psa.SpecificationAttributeOption.SpecificationAttribute.GetLocalized(x => x.Name),
                           SpecificationAttributeOption = psa.SpecificationAttributeOption.GetLocalized(x => x.Name)
                       };
                   }).ToList();
                return model;
            });
        }

        [NonAction]
        protected ProductDetailsModel PrepareProductDetailsPageModel(Product product)
        {
            if (product == null)
                throw new ArgumentNullException("product");

            var model = new ProductDetailsModel()
            {
                Id = product.Id,
                Name = product.GetLocalized(x => x.Name),
                ShortDescription = product.GetLocalized(x => x.ShortDescription),
                FullDescription = product.GetLocalized(x => x.FullDescription),
                MetaKeywords = product.GetLocalized(x => x.MetaKeywords),
                MetaDescription = product.GetLocalized(x => x.MetaDescription),
                MetaTitle = product.GetLocalized(x => x.MetaTitle),
                SeName = product.GetSeName(),
                AgeClass = product.AgeClass,
                ShippingReturnTopic = product.ShippingReturnTopicName
            };

            //template

            var templateCacheKey = string.Format(ModelCacheEventConsumer.PRODUCT_TEMPLATE_MODEL_KEY, product.ProductTemplateId);
            model.ProductTemplateViewPath = _cacheManager.Get(templateCacheKey, () =>
            {
                var template = _productTemplateService.GetProductTemplateById(product.ProductTemplateId);
                if (template == null)
                    template = _productTemplateService.GetAllProductTemplates().FirstOrDefault();
                return template.ViewPath;
            });

            //product review / rating
            model.ProductReviewInfo = new ProductReviewOverviewModel()
            {
                ProductId = product.Id,
                RatingSum = product.ApprovedRatingSum,
                TotalReviews = product.ApprovedTotalReviews,
                AllowCustomerReviews = product.AllowCustomerReviews && ProductReviewEnabledForCustomer(_workContext.CurrentCustomer, product)
            };

            //show specification
            model.HasSpecification = _specificationAttributeService.GetProductSpecificationAttributesByProductId(product.Id, null, true).Count > 0;

            //Shiping Return
            var topicName = string.IsNullOrEmpty(product.ShippingReturnTopicName) ? _catalogSettings.DefaultShippingReturnTopic : product.ShippingReturnTopicName;
            if (!string.IsNullOrEmpty(topicName))
                model.ShippingReturnTopic = topicName;

            //pictures
            model.DefaultPictureZoomEnabled = _mediaSettings.DefaultPictureZoomEnabled;
            var pictures = _pictureService.GetPicturesByProductId(product.Id);
            if (pictures.Count > 0)
            {
                foreach (var picture in pictures)
                {
                    model.PictureModels.Add(new PictureModel()
                    {
                        ImageUrl = _pictureService.GetPictureUrl(picture, _mediaSettings.ProductThumbPictureSizeOnProductDetailsPage),
                        FullSizeImageUrl = _pictureService.GetPictureUrl(picture, 0, true, null, false),
                        Title = string.Format(_localizationService.GetResource("Media.Product.ImageLinkTitleFormat"), model.Name),
                        AlternateText = string.Format(_localizationService.GetResource("Media.Product.ImageAlternateTextFormat"), model.Name),
                    });
                }

                //default picture
                var defaultModel = model.PictureModels.FirstOrDefault();
                if (defaultModel != null)
                {
                    model.DefaultPictureModel = new PictureModel()
                    {
                        ImageUrl = defaultModel.ImageUrl,
                        FullSizeImageUrl = defaultModel.FullSizeImageUrl,
                        Title = defaultModel.Title,
                        AlternateText = defaultModel.AlternateText
                    };
                }
            }
            else
            {
                //no images. set the default one
                model.DefaultPictureModel = new PictureModel()
                {
                    ImageUrl = _pictureService.GetDefaultPictureUrl(_mediaSettings.ProductDetailsPictureSize),
                    FullSizeImageUrl = _pictureService.GetDefaultPictureUrl(),
                    Title = string.Format(_localizationService.GetResource("Media.Product.ImageLinkTitleFormat"), model.Name),
                    AlternateText = string.Format(_localizationService.GetResource("Media.Product.ImageAlternateTextFormat"), model.Name),
                };
            }


            //product variants
            bool allowDirectCheckout = false;
            if (Request.QueryString["directCheckout"] != null)
                Boolean.TryParse(Request.QueryString["directCheckout"], out allowDirectCheckout);
            var allProductVariants = _productService.GetProductVariantsByProductId(product.Id, GetRegionId());
            var productVariants = new List<ProductDetailsModel.ProductVariantModel>();
            foreach (var variant in allProductVariants)
                productVariants.Add(PrepareProductVariantModel(new ProductDetailsModel.ProductVariantModel(), variant, allowDirectCheckout));

            foreach (var pv in productVariants)
            {
                if (!pv.IsOpenBox)
                {
                    pv.OpenBoxProductVariants = productVariants.Where(opv => opv.BoundProductVariantId == pv.Id && opv.IsOpenBox).ToList();
                    if (pv.OpenBoxProductVariants.Count > 0)
                    {
                        decimal? minimalOpenBoxPrice = null;
                        decimal? openBoxPriceBase = null;
                        decimal? openBoxPrice = null;

                        var openBoxProductVariants = allProductVariants.Where(apv => apv.IsOpenBoxProductVariant && apv.BoundProductVariantId == pv.Id).ToList();
                        var openBoxProductVariant = _priceCalculationService.GetProductVariantWithMinimalPrice(openBoxProductVariants, _workContext.CurrentCustomer, true, int.MaxValue, out minimalOpenBoxPrice);
                        List<TaxSurcharge> taxRate;
                        if (minimalOpenBoxPrice.HasValue)
                            openBoxPriceBase = _taxService.GetProductPrice(openBoxProductVariant, minimalOpenBoxPrice.Value, out taxRate);
                        if (openBoxPriceBase.HasValue)
                            openBoxPrice = _currencyService.ConvertFromPrimaryStoreCurrency(openBoxPriceBase.Value, _workContext.WorkingCurrency);
                        if (openBoxPrice.HasValue)
                        {
                            bool displayOpenBoxFromMessage = openBoxProductVariants.Count > 1;
                            if (displayOpenBoxFromMessage)
                                pv.ProductVariantPrice.OpenBoxPrice = String.Format(_localizationService.GetResource("Products.PriceRangeFrom"), _priceFormatter.FormatPrice(openBoxPrice.Value));
                            else
                                pv.ProductVariantPrice.OpenBoxPrice = _priceFormatter.FormatPrice(openBoxPrice.Value);
                        }
                    }
                    model.ProductVariantModels.Add(pv);
                }
            }

            //product stores
            var store = _storeService.GetStoreById(product.StoreId);
            if (store != null)
            {
                var storeModel = store.ToModel();
                model.ProductStore.StorePictureModel.ImageUrl = _pictureService.GetPictureUrl(store.PictureId, _mediaSettings.StoreLogoStorePagePictureSize, true);
                model.ProductStore.StorePictureModel.Title = string.Format(_localizationService.GetResource("Media.Store.ImageLinkTitleFormat"), model.Name);
                model.ProductStore.StorePictureModel.AlternateText = string.Format(_localizationService.GetResource("Media.Store.ImageAlternateTextFormat"), model.Name);
                model.ProductStore.Name = storeModel.Name;
                model.ProductStore.SeName = storeModel.Name;
                model.ProductStore.Url = Url.RouteUrl("Store", new { product.StoreId, SeName = model.ProductStore.SeName });
            }

            return model;
        }

        [NonAction]
        protected void PrepareProductReviewsModel(ProductReviewsModel model, Product product)
        {
            if (product == null)
                throw new ArgumentNullException("product");

            if (model == null)
                throw new ArgumentNullException("model");

            model.ProductId = product.Id;
            model.ProductName = product.GetLocalized(x => x.Name);
            model.ProductSeName = product.GetSeName();

            model.ShortDescription = product.GetLocalized(x => x.ShortDescription);
            model.FullDescription = product.GetLocalized(x => x.FullDescription);

            //pictures
            model.DefaultPictureZoomEnabled = _mediaSettings.DefaultPictureZoomEnabled;
            var pictures = _pictureService.GetPicturesByProductId(product.Id);
            if (pictures.Count > 0)
            {
                //default picture
                model.DefaultPictureModel = new PictureModel()
                {
                    ImageUrl = _pictureService.GetPictureUrl(pictures.FirstOrDefault(), _mediaSettings.ProductDetailsPictureSize),
                    FullSizeImageUrl = _pictureService.GetPictureUrl(pictures.FirstOrDefault()),
                    Title = string.Format(_localizationService.GetResource("Media.Product.ImageLinkTitleFormat"), model.ProductName),
                    AlternateText = string.Format(_localizationService.GetResource("Media.Product.ImageAlternateTextFormat"), model.ProductName),
                };
                //all pictures
                foreach (var picture in pictures)
                {
                    model.PictureModels.Add(new PictureModel()
                    {
                        ImageUrl = _pictureService.GetPictureUrl(picture, _mediaSettings.ProductThumbPictureSizeOnProductDetailsPage),
                        FullSizeImageUrl = _pictureService.GetPictureUrl(picture),
                        Title = string.Format(_localizationService.GetResource("Media.Product.ImageLinkTitleFormat"), model.ProductName),
                        AlternateText = string.Format(_localizationService.GetResource("Media.Product.ImageAlternateTextFormat"), model.ProductName),
                    });
                }
            }
            else
            {
                //no images. set the default one
                model.DefaultPictureModel = new PictureModel()
                {
                    ImageUrl = _pictureService.GetDefaultPictureUrl(_mediaSettings.ProductDetailsPictureSize),
                    FullSizeImageUrl = _pictureService.GetDefaultPictureUrl(),
                    Title = string.Format(_localizationService.GetResource("Media.Product.ImageLinkTitleFormat"), model.ProductName),
                    AlternateText = string.Format(_localizationService.GetResource("Media.Product.ImageAlternateTextFormat"), model.ProductName),
                };
            }

            var productReviews = product.ProductReviews.Where(pr => pr.IsApproved).OrderBy(pr => pr.CreatedOnUtc);
            foreach (var pr in productReviews)
            {
                model.Items.Add(new ProductReviewModel()
                {
                    Id = pr.Id,
                    CustomerId = pr.CustomerId,
                    CustomerName = pr.Customer.GetFirstName(),
                    AllowViewingProfiles = _customerSettings.AllowViewingProfiles && pr.Customer != null && !pr.Customer.IsGuest(),
                    Title = pr.Title,
                    ReviewText = pr.ReviewText,
                    Rating = pr.Rating,
                    Helpfulness = new ProductReviewHelpfulnessModel()
                    {
                        ProductReviewId = pr.Id,
                        HelpfulYesTotal = pr.HelpfulYesTotal,
                        HelpfulNoTotal = pr.HelpfulNoTotal,
                    },
                    WrittenOnStr = _dateTimeHelper.ConvertToUserTime(pr.CreatedOnUtc, DateTimeKind.Utc).ToString("g"),
                });
            }

            model.AddProductReview.CanCurrentCustomerLeaveReview = _catalogSettings.AllowAnonymousUsersToReviewProduct || !_workContext.CurrentCustomer.IsGuest();
            model.AddProductReview.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnProductReviewPage;
        }

        [NonAction]
        protected ProductDetailsModel.ProductVariantModel PrepareProductVariantModel(ProductDetailsModel.ProductVariantModel model, ProductVariant productVariant, bool allowDirectCheckout = false)
        {
            if (productVariant == null)
                throw new ArgumentNullException("productVariant");

            if (model == null)
                throw new ArgumentNullException("model");

            var customer = _workContext.CurrentCustomer;

            #region Properties

            var stockMessage = String.Empty;
            var stockLevel = String.Empty;
            productVariant.FormatStockMessage(_localizationService, out stockMessage, out stockLevel);

            model.Id = productVariant.Id;
            model.Name = productVariant.GetLocalized(x => x.Name);
            model.ShowSku = _catalogSettings.ShowProductSku;
            model.Sku = productVariant.Sku;
            model.Description = productVariant.GetLocalized(x => x.Description);
            model.ProductId = productVariant.ProductId;
            model.ShowManufacturerPartNumber = _catalogSettings.ShowManufacturerPartNumber;
            model.ManufacturerPartNumber = productVariant.ManufacturerPartNumber;
            model.ShowGtin = _catalogSettings.ShowGtin;
            model.Gtin = productVariant.Gtin;
            model.DisplayStockQuantity = productVariant.DisplayStockQuantity;
            model.StockQuantity = productVariant.StockQuantity;
            model.StockAvailablity = stockMessage;
            model.StockLevel = stockLevel;
            model.PictureModel.FullSizeImageUrl = _pictureService.GetPictureUrl(productVariant.PictureId, 0, false);
            model.PictureModel.ImageUrl = _pictureService.GetPictureUrl(productVariant.PictureId, _mediaSettings.ProductVariantPictureSize, false);
            model.PictureModel.Title = string.Format(_localizationService.GetResource("Media.Product.ImageLinkTitleFormat"), model.Name);
            model.PictureModel.AlternateText = string.Format(_localizationService.GetResource("Media.Product.ImageAlternateTextFormat"), model.Name);
            model.HasSampleDownload = productVariant.IsDownload && productVariant.HasSampleDownload;
            model.IsCurrentCustomerRegistered = _workContext.CurrentCustomer.IsRegistered();
            model.IsOpenBox = productVariant.IsOpenBoxProductVariant;
            model.AllowedReserved = productVariant.AllowedReserved;
            if (productVariant.IsOpenBoxProductVariant)
                model.BoundProductVariantId = productVariant.BoundProductVariantId;
            //back in stock subscriptions)
            if (productVariant.ManageInventoryMethod == ManageInventoryMethod.ManageStock &&
                productVariant.BackorderMode == BackorderMode.NoBackorders &&
                productVariant.AllowBackInStockSubscriptions &&
                productVariant.StockQuantity <= 0)
            {
                //out of stock
                model.DisplayBackInStockSubscription = true;
                model.BackInStockAlreadySubscribed = _backInStockSubscriptionService.FindSubscription(_workContext.CurrentCustomer.Id, productVariant.Id) != null;
            }

            #endregion

            #region Product variant price
            model.ProductVariantPrice.ProductVariantId = productVariant.Id;
            model.ProductVariantPrice.DynamicPriceUpdate = _catalogSettings.EnableDynamicPriceUpdate;
            if (_permissionService.Authorize(StandardPermissionProvider.DisplayPrices))
            {
                model.ProductVariantPrice.HidePrices = false;
                if (productVariant.CustomerEntersPrice)
                {
                    model.ProductVariantPrice.CustomerEntersPrice = true;
                }
                else if (productVariant.CallForPrice)
                {
                    model.ProductVariantPrice.CallForPrice = true;
                }
                else
                {
                    List<TaxSurcharge> taxRate;
                    decimal oldPriceBase = _taxService.GetProductPrice(productVariant, productVariant.OldPrice, out taxRate);
                    decimal finalPriceWithoutDiscountBase = _taxService.GetProductPrice(productVariant, _priceCalculationService.GetFinalPrice(productVariant, false), out taxRate);
                    decimal finalPriceWithDiscountBase = _taxService.GetProductPrice(productVariant, _priceCalculationService.GetFinalPrice(productVariant, true), out taxRate);

                    decimal oldPrice = _currencyService.ConvertFromPrimaryStoreCurrency(oldPriceBase, _workContext.WorkingCurrency);
                    decimal finalPriceWithoutDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceWithoutDiscountBase, _workContext.WorkingCurrency);
                    decimal finalPriceWithDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceWithDiscountBase, _workContext.WorkingCurrency);

                    if (finalPriceWithoutDiscountBase != oldPriceBase && oldPriceBase > decimal.Zero)
                        model.ProductVariantPrice.OldPrice = _priceFormatter.FormatPrice(oldPrice);

                    model.ProductVariantPrice.Price = _priceFormatter.FormatPrice(finalPriceWithoutDiscount);

                    if (finalPriceWithoutDiscountBase != finalPriceWithDiscountBase)
                        model.ProductVariantPrice.PriceWithDiscount = _priceFormatter.FormatPrice(finalPriceWithDiscount);

                    model.ProductVariantPrice.OldPriceValue = oldPrice;
                    model.ProductVariantPrice.PriceValue = finalPriceWithoutDiscount;
                    model.ProductVariantPrice.PriceWithDiscountValue = finalPriceWithDiscount;

                    if (productVariant.HasTierPrices)
                    {
                        var tierPrices = productVariant.TierPrices.ToList();
                        if (tierPrices.Count > 0)
                        {
                            var tierPriceBase = tierPrices.First().Price;
                            var tierPrice = _currencyService.ConvertFromPrimaryStoreCurrency(tierPriceBase, _workContext.WorkingCurrency);
                            model.ProductVariantPrice.MemberTierPriceValue = tierPrice;
                            model.ProductVariantPrice.MemberTierPrice = _priceFormatter.FormatPrice(tierPrice);
                            model.ProductVariantPrice.IsMemberTier = tierPrices.FilterForCustomer(customer).Count > 0;
                        }
                    }
                }
            }
            else
            {
                model.ProductVariantPrice.HidePrices = true;
                model.ProductVariantPrice.OldPrice = null;
                model.ProductVariantPrice.Price = null;
            }
            #endregion

            #region 'Add to cart' model


            #region Display or not ReserveButton

            if (model.AllowedReserved)
            {
                string reserveRequirementSystemName = "DiscountRequirement.ReserveDiscount";
                var discounts = productVariant.AppliedDiscounts.Where(x => x.DiscountRequirements.Any(y => y.DiscountRequirementRuleSystemName == reserveRequirementSystemName));
                if (discounts != null && discounts.Count() > 0)
                {
                    var requirementRule = _discountService.LoadDiscountRequirementRuleBySystemName(reserveRequirementSystemName);
                    var requirment = discounts.FirstOrDefault().DiscountRequirements.Where(x => x.DiscountRequirementRuleSystemName == reserveRequirementSystemName).FirstOrDefault();
                    var request = new CheckDiscountRequirementRequest()
                    {
                        DiscountRequirement = requirment,
                        Customer = _workContext.CurrentCustomer,
                        ProductVariant = productVariant
                    };
                    if (requirementRule.CheckRequirement(request))
                        model.AddToCart.AvailabldForReserve = true;
                    else
                        model.AddToCart.AvailabldForReserve = false;
                }
                else
                    model.AddToCart.AvailabldForReserve = false;
            }
            #endregion
            model.AddToCart.ProductVariantId = productVariant.Id;

            //the new profile of add to car model
            model.AddToCart.StoreId = productVariant.Product.StoreId;

            //model.AddToCart.AvailabldForReserve = productVariant.AllowedReserved;

            //quantity
            model.AddToCart.EnteredQuantity = productVariant.OrderMinimumQuantity;

            //'add to cart', 'add to wishlist' buttons
            model.AddToCart.DisableBuyButton = productVariant.DisableBuyButton || !_permissionService.Authorize(StandardPermissionProvider.EnableShoppingCart);
            model.AddToCart.DisableWishlistButton = productVariant.DisableWishlistButton || !_permissionService.Authorize(StandardPermissionProvider.EnableWishlist);
            if (!_permissionService.Authorize(StandardPermissionProvider.DisplayPrices))
            {
                model.AddToCart.DisableBuyButton = true;
                model.AddToCart.DisableWishlistButton = true;
            }
            model.AddToCart.AllowDirectCheckout = allowDirectCheckout;

            //pre-order
            model.AddToCart.AvailableForPreOrder = productVariant.AvailableForPreOrder;

            //customer entered price
            model.AddToCart.CustomerEntersPrice = productVariant.CustomerEntersPrice;
            if (model.AddToCart.CustomerEntersPrice)
            {
                decimal minimumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(productVariant.MinimumCustomerEnteredPrice, _workContext.WorkingCurrency);
                decimal maximumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(productVariant.MaximumCustomerEnteredPrice, _workContext.WorkingCurrency);

                model.AddToCart.CustomerEnteredPrice = minimumCustomerEnteredPrice;
                model.AddToCart.CustomerEnteredPriceRange = string.Format(_localizationService.GetResource("Products.EnterProductPrice.Range"),
                    _priceFormatter.FormatPrice(minimumCustomerEnteredPrice, false, false),
                    _priceFormatter.FormatPrice(maximumCustomerEnteredPrice, false, false));
            }
            //allowed quantities
            var allowedQuantities = productVariant.ParseAllowedQuatities();
            foreach (var qty in allowedQuantities)
            {
                model.AddToCart.AllowedQuantities.Add(new SelectListItem()
                {
                    Text = qty.ToString(),
                    Value = qty.ToString()
                });
            }

            #endregion

            #region Gift card

            model.GiftCard.IsGiftCard = productVariant.IsGiftCard;
            if (model.GiftCard.IsGiftCard)
            {
                model.GiftCard.GiftCardType = productVariant.GiftCardType;
                model.GiftCard.SenderName = _workContext.CurrentCustomer.GetFullName();
                model.GiftCard.SenderEmail = _workContext.CurrentCustomer.Email;
            }

            #endregion

            #region Product attributes

            var productVariantAttributes = _productAttributeService.GetProductVariantAttributesByProductVariantId(productVariant.Id);
            foreach (var attribute in productVariantAttributes)
            {
                var pvaModel = new ProductDetailsModel.ProductVariantModel.ProductVariantAttributeModel()
                    {
                        Id = attribute.Id,
                        ProductVariantId = productVariant.Id,
                        ProductAttributeId = attribute.ProductAttributeId,
                        Name = attribute.ProductAttribute.GetLocalized(x => x.Name),
                        Description = attribute.ProductAttribute.GetLocalized(x => x.Description),
                        TextPrompt = attribute.TextPrompt,
                        IsRequired = attribute.IsRequired,
                        AttributeControlType = attribute.AttributeControlType,
                        AllowedFileExtensions = _catalogSettings.FileUploadAllowedExtensions,
                    };

                if (attribute.ShouldHaveValues())
                {
                    //values
                    var pvaValues = _productAttributeService.GetProductVariantAttributeValues(attribute.Id);
                    foreach (var pvaValue in pvaValues)
                    {
                        var pvaValueModel = new ProductDetailsModel.ProductVariantModel.ProductVariantAttributeValueModel()
                        {
                            Id = pvaValue.Id,
                            Name = pvaValue.GetLocalized(x => x.Name),
                            IsPreSelected = pvaValue.IsPreSelected,
                        };
                        pvaModel.Values.Add(pvaValueModel);

                        //display price if allowed
                        if (_permissionService.Authorize(StandardPermissionProvider.DisplayPrices))
                        {
                            List<TaxSurcharge> taxRate;
                            decimal priceAdjustmentBase = _taxService.GetProductPrice(productVariant, pvaValue.PriceAdjustment, out taxRate);
                            decimal priceAdjustment = _currencyService.ConvertFromPrimaryStoreCurrency(priceAdjustmentBase, _workContext.WorkingCurrency);
                            if (priceAdjustmentBase > decimal.Zero)
                                pvaValueModel.PriceAdjustment = "+" + _priceFormatter.FormatPrice(priceAdjustment, false, false);
                            else if (priceAdjustmentBase < decimal.Zero)
                                pvaValueModel.PriceAdjustment = "-" + _priceFormatter.FormatPrice(-priceAdjustment, false, false);

                            pvaValueModel.PriceAdjustmentValue = priceAdjustment;
                        }
                    }
                }

                model.ProductVariantAttributes.Add(pvaModel);
            }

            #endregion

            return model;
        }

        [NonAction]
        protected FavoriteStoreModel PrepareFavoriteStoreModel(Customer customer)
        {
            var model = new FavoriteStoreModel();
            var stores = _storeService.GetFavoriteStores(customer);
            model.Stores = stores.Select(s => new FavoriteStoreModel.StoreModel()
            {
                Picture = new PictureModel()
                {
                    ImageUrl = _pictureService.GetPictureUrl(s.PictureId, _mediaSettings.FavoriteStoreThumbPictureSize, true),
                    Title = string.Format(_localizationService.GetResource("Media.Store.ImageLinkTitleFormat"), s.Name),
                    AlternateText = string.Format(_localizationService.GetResource("Media.Store.ImageAlternateTextFormat"), s.Name)
                },
                StoreId = s.Id,
                StoreName = s.Name,
                StoreSeName = s.SeName
            }).ToList();

            return model;
        }

        [NonAction]
        protected bool ProductReviewEnabledForCustomer(Customer customer, Product product)
        {
            var result = false;
            if (product != null)
            {
                var opvs = _orderService.GetAllOrderProductVariants(null, customer.Id, null, null, null, null, null);
                opvs = opvs.Where(o => o.ProductVariant.ProductId == product.Id && o.Order.OrderKind == OrderKind.Commerce).ToList();
                if (opvs.Count() > 0)
                {
                    result = true;
                }

            }

            return result;
        }

        
        #endregion

        #region Categories

        [NopHttpsRequirement(SslRequirement.No)]
        public ActionResult Category(int categoryId, CatalogPagingFilteringModel command, int storeId = 1)
        {
            var category = _categoryService.GetCategoryById(categoryId);
            if (category == null || category.Deleted)
                return RedirectToRoute("HomePage");

            //Check whether the current user has a "Manage catalog" permission
            //It allows him to preview a category before publishing
            if (!category.Published && !_permissionService.Authorize(StandardPermissionProvider.ManageCatalog))
                return RedirectToRoute("HomePage");

            //ACL (access control list)
            if (!_aclService.Authorize(category))
                return RedirectToRoute("HomePage");

            //'Continue shopping' URL
            _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.LastContinueShoppingPage, _webHelper.GetThisPageUrl(false));

            if (category != null)
                return Redirect(String.Format("/Search?cid={0}&sc=1", categoryId));


            if (command.PageNumber <= 0) command.PageNumber = 1;

            var model = category.ToModel();

            //sorting
            model.PagingFilteringContext.AllowProductSorting = _catalogSettings.AllowProductSorting;
            if (model.PagingFilteringContext.AllowProductSorting)
            {
                foreach (ProductSortingEnum enumValue in Enum.GetValues(typeof(ProductSortingEnum)))
                {
                    var currentPageUrl = _webHelper.GetThisPageUrl(true);
                    var sortUrl = _webHelper.ModifyQueryString(currentPageUrl, "orderby=" + ((int)enumValue).ToString(), null);

                    var sortValue = enumValue.GetLocalizedEnum(_localizationService, _workContext);
                    model.PagingFilteringContext.AvailableSortOptions.Add(new SelectListItem()
                        {
                            Text = sortValue,
                            Value = sortUrl,
                            Selected = enumValue == (ProductSortingEnum)command.OrderBy
                        });
                }
            }



            //view mode
            model.PagingFilteringContext.AllowProductViewModeChanging = _catalogSettings.AllowProductViewModeChanging;
            var viewMode = !string.IsNullOrEmpty(command.ViewMode)
                ? command.ViewMode
                : _catalogSettings.DefaultViewMode;
            if (model.PagingFilteringContext.AllowProductViewModeChanging)
            {
                var currentPageUrl = _webHelper.GetThisPageUrl(true);
                //grid
                model.PagingFilteringContext.AvailableViewModes.Add(new SelectListItem()
                {
                    Text = _localizationService.GetResource("Categories.ViewMode.Grid"),
                    Value = _webHelper.ModifyQueryString(currentPageUrl, "viewmode=grid", null),
                    Selected = viewMode == "grid"
                });
                //list
                model.PagingFilteringContext.AvailableViewModes.Add(new SelectListItem()
                {
                    Text = _localizationService.GetResource("Categories.ViewMode.List"),
                    Value = _webHelper.ModifyQueryString(currentPageUrl, "viewmode=list", null),
                    Selected = viewMode == "list"
                });
            }

            //page size
            model.PagingFilteringContext.AllowCustomersToSelectPageSize = false;
            if (category.AllowCustomersToSelectPageSize && category.PageSizeOptions != null)
            {
                var pageSizes = category.PageSizeOptions.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);

                if (pageSizes.Any())
                {
                    // get the first page size entry to use as the default (category page load) or if customer enters invalid value via query string
                    if (command.PageSize <= 0 || !pageSizes.Contains(command.PageSize.ToString()))
                    {
                        int temp = 0;

                        if (int.TryParse(pageSizes.FirstOrDefault(), out temp))
                        {
                            if (temp > 0)
                            {
                                command.PageSize = temp;
                            }
                        }
                    }

                    var currentPageUrl = _webHelper.GetThisPageUrl(true);
                    var sortUrl = _webHelper.ModifyQueryString(currentPageUrl, "pagesize={0}", null);
                    sortUrl = _webHelper.RemoveQueryString(sortUrl, "pagenumber");

                    foreach (var pageSize in pageSizes)
                    {
                        int temp = 0;
                        if (!int.TryParse(pageSize, out temp))
                        {
                            continue;
                        }
                        if (temp <= 0)
                        {
                            continue;
                        }

                        model.PagingFilteringContext.PageSizeOptions.Add(new SelectListItem()
                        {
                            Text = pageSize,
                            Value = String.Format(sortUrl, pageSize),
                            Selected = pageSize.Equals(command.PageSize.ToString(), StringComparison.InvariantCultureIgnoreCase)
                        });
                    }

                    if (model.PagingFilteringContext.PageSizeOptions.Any())
                    {
                        model.PagingFilteringContext.PageSizeOptions = model.PagingFilteringContext.PageSizeOptions.OrderBy(x => int.Parse(x.Text)).ToList();
                        model.PagingFilteringContext.AllowCustomersToSelectPageSize = true;

                        if (command.PageSize <= 0)
                        {
                            command.PageSize = int.Parse(model.PagingFilteringContext.PageSizeOptions.FirstOrDefault().Text);
                        }
                    }
                }
            }
            else
            {
                //customer is not allowed to select a page size
                command.PageSize = category.PageSize;
            }

            if (command.PageSize <= 0) command.PageSize = category.PageSize;


            //price ranges
            model.PagingFilteringContext.PriceRangeFilter.LoadPriceRangeFilters(category.PriceRanges, _webHelper, _priceFormatter);
            var selectedPriceRange = model.PagingFilteringContext.PriceRangeFilter.GetSelectedPriceRange(_webHelper, category.PriceRanges);
            decimal? minPriceConverted = null;
            decimal? maxPriceConverted = null;
            if (selectedPriceRange != null)
            {
                if (selectedPriceRange.From.HasValue)
                    minPriceConverted = _currencyService.ConvertToPrimaryStoreCurrency(selectedPriceRange.From.Value, _workContext.WorkingCurrency);

                if (selectedPriceRange.To.HasValue)
                    maxPriceConverted = _currencyService.ConvertToPrimaryStoreCurrency(selectedPriceRange.To.Value, _workContext.WorkingCurrency);
            }





            //category breadcrumb
            model.DisplayCategoryBreadcrumb = _catalogSettings.CategoryBreadcrumbEnabled;
            if (model.DisplayCategoryBreadcrumb)
            {
                foreach (var catBr in GetCategoryBreadCrumb(category))
                {
                    model.CategoryBreadcrumb.Add(new CategoryModel()
                    {
                        Id = catBr.Id,
                        Name = catBr.GetLocalized(x => x.Name),
                        SeName = catBr.GetSeName()
                    });
                }
            }


            //category picture
            model.PictureModel = PreparePictureModel(category.PictureId, category.Id, model.Name);

            //subcategories
            model.SubCategories = _categoryService
                .GetAllCategoriesByParentCategoryId(categoryId)
                .Select(x =>
                {
                    var subCatName = x.GetLocalized(y => y.Name);
                    var subCatModel = new CategoryModel.SubCategoryModel()
                    {
                        Id = x.Id,
                        Name = subCatName,
                        SeName = x.GetSeName(),
                    };

                    //prepare picture model
                    subCatModel.PictureModel = PreparePictureModel(x.PictureId, x.Id, subCatName);

                    return subCatModel;
                })
                .ToList();

            //featured products
            //Question: should we use '_catalogSettings.ShowProductsFromSubcategories' setting for displaying featured products?
            if (!_catalogSettings.IgnoreFeaturedProducts && _categoryService.GetTotalNumberOfFeaturedProducts(categoryId) > 0)
            {
                //We use the fast GetTotalNumberOfFeaturedProducts before invoking of the slow SearchProducts
                //to ensure that we have at least one featured product
                IList<int> filterableSpecificationAttributeOptionIdsFeatured = null;
                var featuredProducts = _productService.SearchProducts(GetRegionId(), category.Id,
                    0, true, null, null, 0, null, false, false,
                    _workContext.WorkingLanguage.Id, null,
                    ProductSortingEnum.Position, 0, int.MaxValue,
                    false, out filterableSpecificationAttributeOptionIdsFeatured, storeId: storeId);
                model.FeaturedProducts = PrepareProductOverviewModels(featuredProducts).ToList();
            }


            var categoryIds = new List<int>();
            categoryIds.Add(category.Id);
            if (_catalogSettings.ShowProductsFromSubcategories)
            {
                //include subcategories
                categoryIds.AddRange(GetChildCategoryIds(category.Id));
            }
            //products
            IList<int> alreadyFilteredSpecOptionIds = model.PagingFilteringContext.SpecificationFilter.GetAlreadyFilteredSpecOptionIds(_webHelper);
            IList<int> filterableSpecificationAttributeOptionIds = null;
            var products = _productService.SearchProducts(GetRegionId(), null, categoryIds, 0,
                _catalogSettings.IncludeFeaturedProductsInNormalLists ? null : (bool?)false,
                minPriceConverted, maxPriceConverted,
                0, string.Empty, false, false, _workContext.WorkingLanguage.Id, alreadyFilteredSpecOptionIds,
                (ProductSortingEnum)command.OrderBy, command.PageNumber - 1, command.PageSize,
                true, out filterableSpecificationAttributeOptionIds, storeId: storeId);
            model.Products = PrepareProductOverviewModels(products).ToList();

            model.PagingFilteringContext.LoadPagedList(products);
            model.PagingFilteringContext.ViewMode = viewMode;

            //specs
            model.PagingFilteringContext.SpecificationFilter.PrepareSpecsFilters(alreadyFilteredSpecOptionIds,
                filterableSpecificationAttributeOptionIds,
                _specificationAttributeService, _webHelper, _workContext);


            //template
            var templateCacheKey = string.Format(ModelCacheEventConsumer.CATEGORY_TEMPLATE_MODEL_KEY, category.CategoryTemplateId);
            var templateViewPath = _cacheManager.Get(templateCacheKey, () =>
                {
                    var template = _categoryTemplateService.GetCategoryTemplateById(category.CategoryTemplateId);
                    if (template == null)
                        template = _categoryTemplateService.GetAllCategoryTemplates().FirstOrDefault();
                    return template.ViewPath;
                });

            //activity log
            _customerActivityService.InsertActivity("PublicStore.ViewCategory", _localizationService.GetResource("ActivityLog.PublicStore.ViewCategory"), category.Name);

            return View(templateViewPath, model);
        }

        [ChildActionOnly]
        public ActionResult CategoryNavigation(int currentCategoryId, int currentProductId, int storeId = 1)
        {
            var customerRolesIds = _workContext.CurrentCustomer.CustomerRoleMappings
                .Where(cr => cr.CustomerRole.Active).Select(cr => cr.Id).ToList();
            string cacheKey = string.Format(ModelCacheEventConsumer.CATEGORY_NAVIGATION_MODEL_KEY, currentCategoryId, currentProductId, _workContext.WorkingLanguage.Id, storeId, string.Join(",", customerRolesIds));
            var cacheModel = _cacheManager.Get(cacheKey, () =>
            {
                //get current category
                var currentCategory = _categoryService.GetCategoryById(currentCategoryId);
                if (currentCategory == null && currentProductId > 0)
                {
                    var productCategories = _categoryService.GetProductCategoriesByProductId(currentProductId);
                    if (productCategories.Count > 0)
                        currentCategory = productCategories[0].Category;
                }

                //prepare model
                var model = new CategoryNavigationModel();
                model.StoreId = storeId;
                model.CurrentCategoryId = currentCategory != null ? currentCategory.Id : 0;
                var breadCrumb = currentCategory != null ? GetCategoryBreadCrumb(currentCategory) : new List<Category>();
                model.Categories.AddRange(PrepareCategoryNavigationModel(breadCrumb, 0, 0));
                return model;
            }
            );

            return PartialView(cacheModel);
        }

        public ActionResult Categories()
        {
            return View();
        }

        [ChildActionOnly]
        public ActionResult TopMenu()
        {
            //TODO: Event control for update cache when category is changing
            string cacheKey = string.Format("spotshub.topmenu.langid-{0}", _workContext.WorkingLanguage.Id);
            var cachedModel = _cacheManager.Get(cacheKey, () =>
            {
                return PrepareCategorySimpleModels(
                    _categoryService.GetAllCategoriesDisplayedOnHomePage()).ToList();
            });

            var model = new TopMenuModel()
            {
                Categories = cachedModel,
                LogisticsEnabled = false
            };
            return PartialView(model);
        }

        [ChildActionOnly]
		[OutputCache(VaryByParam = "*", Duration = 600)]
        public ActionResult CategoryNavigationHome(int storeId = 1)
        {
            var categories = _categoryService.GetAllCategoriesDisplayedOnHomePage();
            var model = categories.Select(x =>
                {
                    var m = x.ToModel();

                    //prepare picture model
                    int pictureSize = _mediaSettings.CategoryThumbPictureSize;
                    var categoryPictureCacheKey = string.Format(ModelCacheEventConsumer.CATEGORY_PICTURE_MODEL_KEY, x.Id, pictureSize, true, _workContext.WorkingLanguage.Id, _webHelper.IsCurrentConnectionSecured());
                    m.PictureModel = _cacheManager.Get(categoryPictureCacheKey, () =>
                    {
                        var pictureModel = new PictureModel()
                        {
                            FullSizeImageUrl = _pictureService.GetPictureUrl(x.PictureId),
                            ImageUrl = _pictureService.GetPictureUrl(x.PictureId, pictureSize),
                            Title = string.Format(_localizationService.GetResource("Media.Category.ImageLinkTitleFormat"), m.Name),
                            AlternateText = string.Format(_localizationService.GetResource("Media.Category.ImageAlternateTextFormat"), m.Name)
                        };
                        return pictureModel;
                    });
                    return m;
                }).ToList();
            return PartialView(model);

            //List<CategoryModel> listModel = null;

            ////get current category
            //var topCategoryList = _categoryService.GetAllTopLevelCategories();
            //if (topCategoryList == null || topCategoryList.Count() == 0)
            //{
            //    return PartialView(listModel);
            //}

            ////prepare model
            //const int maxNumModels = 2;  // front-end only has spots for 2 top categories
            //var numModels = topCategoryList.Count() < maxNumModels ? topCategoryList.Count() : maxNumModels;
            //listModel = new List<CategoryModel>(numModels);

            //foreach (var x in topCategoryList)
            //{
            //    var model = x.ToModel();

            //    //prepare picture model
            //    int pictureSize = _mediaSettings.CategoryThumbPictureSize;
            //    var categoryPictureCacheKey = string.Format(ModelCacheEventConsumer.CATEGORY_PICTURE_MODEL_KEY, x.Id, pictureSize, true, _workContext.WorkingLanguage.Id, _webHelper.IsCurrentConnectionSecured());
            //    model.PictureModel = _cacheManager.Get(categoryPictureCacheKey, () =>
            //    {
            //        var pictureModel = new PictureModel()
            //        {
            //            FullSizeImageUrl = _pictureService.GetPictureUrl(x.PictureId),
            //            ImageUrl = _pictureService.GetPictureUrl(x.PictureId, pictureSize),
            //            Title = string.Format(_localizationService.GetResource("Media.Category.ImageLinkTitleFormat"), model.Name),
            //            AlternateText = string.Format(_localizationService.GetResource("Media.Category.ImageAlternateTextFormat"), model.Name)
            //        };
            //        return pictureModel;
            //    });

            //    //subcategories
            //    var subCategories = _categoryService.GetAllCategoriesByParentCategoryId(model.Id, false, _catalogSettings.HideEmptyCategory);

            //    // TODO: Added this condition to make sure only display top-level categories that actually
            //    //       have sub-categories (to actually make this metohd interesting!)
            //    //       Not sure how we would want to select which 2, among many, top-level categories to display
            //    if (subCategories.Count == 0)
            //        continue;

            //    model.SubCategories = subCategories
            //        .Select(c =>
            //        {
            //            var subCatName = c.GetLocalized(y => y.Name);
            //            var subCatDesc = c.GetLocalized(y => y.Description);
            //            var subCatModel = new CategoryModel.SubCategoryModel()
            //            {
            //                Id = c.Id,
            //                Name = subCatName,
            //                SeName = c.GetSeName(),
            //                Description = subCatDesc
            //            };

            //            //prepare picture model
            //            subCatModel.PictureModel = PreparePictureModel(c.PictureId, c.Id, subCatName);

            //            return subCatModel;
            //        })
            //        .ToList();

            //    //subcategories of subcategories
            //    foreach (var scat in model.SubCategories)
            //    {
            //        scat.SubCategories = _categoryService
            //            .GetAllCategoriesByParentCategoryId(scat.Id, false, _catalogSettings.HideEmptyCategory)
            //            .Select(c =>
            //            {
            //                var subCatName = c.GetLocalized(y => y.Name);
            //                var subCatDesc = c.GetLocalized(y => y.Description);
            //                var subCatModel = new CategoryModel.SubCategoryModel()
            //                {
            //                    Id = c.Id,
            //                    Name = subCatName,
            //                    SeName = c.GetSeName(),
            //                    Description = subCatDesc
            //                };

            //                //prepare picture model
            //                subCatModel.PictureModel = PreparePictureModel(c.PictureId, c.Id, subCatName);

            //                return subCatModel;
            //            })
            //            .ToList();
            //    }

            //    listModel.Add(model);

            //    if (listModel.Count >= numModels)
            //        break;
            //}

            //return PartialView(listModel);
        }

        [ChildActionOnly]
        public ActionResult HomepageCategories()
        {
            var categories = _categoryService.GetAllCategoriesDisplayedOnHomePage();
            //ACL
            categories = categories.Where(c => _aclService.Authorize(c)).ToList();

            var listModel = categories
                .Select(x =>
                {
                    var catModel = x.ToModel();

                    //prepare picture model
                    int pictureSize = _mediaSettings.CategoryThumbPictureSize;
                    var categoryPictureCacheKey = string.Format(ModelCacheEventConsumer.CATEGORY_PICTURE_MODEL_KEY, x.Id, pictureSize, true, _workContext.WorkingLanguage.Id, _webHelper.IsCurrentConnectionSecured());
                    catModel.PictureModel = _cacheManager.Get(categoryPictureCacheKey, () =>
                    {
                        var pictureModel = new PictureModel()
                        {
                            FullSizeImageUrl = _pictureService.GetPictureUrl(x.PictureId),
                            ImageUrl = _pictureService.GetPictureUrl(x.PictureId, pictureSize),
                            Title = string.Format(_localizationService.GetResource("Media.Category.ImageLinkTitleFormat"), catModel.Name),
                            AlternateText = string.Format(_localizationService.GetResource("Media.Category.ImageAlternateTextFormat"), catModel.Name)
                        };
                        return pictureModel;
                    });

                    return catModel;
                })
                .ToList();

            return PartialView(listModel);
        }

        [NonAction]
        private PictureModel PreparePictureModel(int pictureId, int categoryId, string categoryName)
        {
            int pictureSize = _mediaSettings.CategoryThumbPictureSize;
            var categoryPictureCacheKey = string.Format(ModelCacheEventConsumer.CATEGORY_PICTURE_MODEL_KEY, categoryId, pictureSize, true, _workContext.WorkingLanguage.Id, _webHelper.IsCurrentConnectionSecured());
            var model = _cacheManager.Get(categoryPictureCacheKey, () =>
            {
                var pictureModel = new PictureModel()
                {
                    FullSizeImageUrl = _pictureService.GetPictureUrl(pictureId),
                    ImageUrl = _pictureService.GetPictureUrl(pictureId, pictureSize),
                    Title = string.Format(_localizationService.GetResource("Media.Category.ImageLinkTitleFormat"), categoryName),
                    AlternateText = string.Format(_localizationService.GetResource("Media.Category.ImageAlternateTextFormat"), categoryName)
                };
                return pictureModel;
            });
            return model;
        }

        [NonAction]
        protected int GetCategoryProductNumber(int categoryId)
        {
            // TODO: Event to consume the cache change
            string cacheKey = string.Format("spotshub.categoryproductnum.categoryid-{0}", categoryId);
            var cachedModel = _cacheManager.Get(cacheKey, () =>
            {
                var categoryIds = new List<int>();
                categoryIds.Add(categoryId);
                //include subcategories
                if (_catalogSettings.ShowCategoryProductNumberIncludingSubcategories)
                    categoryIds.AddRange(GetChildCategoryIds(categoryId));

                IList<int> filterableSpecificationAttributeOptionIds = null;
                var numberOfProducts = _productService
                    .SearchProducts(GetRegionId(), null, categoryIds, 0,
                    _catalogSettings.IncludeFeaturedProductsInNormalLists ? null : (bool?)false,
                    null, null,
                    0, string.Empty, false, false, _workContext.WorkingLanguage.Id, null, ProductSortingEnum.CreatedOn
                    , 0, int.MaxValue,
                    true, out filterableSpecificationAttributeOptionIds)
                    .TotalCount;
                return numberOfProducts;
            });
            return cachedModel;
        }

        /// <summary>
        /// Prepare category (simple) models
        /// </summary>
        /// <param name="rootCategoryId">Root category identifier</param>
        /// <param name="loadSubCategoriesForIds">Load subcategories only for the specified category IDs; pass null to load subcategories for all categories</param>
        /// <param name="level">Current level</param>
        /// <param name="levelsToLoad">A value indicating how many levels to load (max)</param>
        /// <param name="validateIncludeInTopMenu">A value indicating whether we should validate "include in top menu" property</param>
        /// <returns>Category models</returns>
        [NonAction]
        protected IList<CategorySimpleModel> PrepareCategorySimpleModels(int rootCategoryId,
            IList<int> loadSubCategoriesForIds, int level, int levelsToLoad, bool validateIncludeInTopMenu)
        {
            var result = new List<CategorySimpleModel>();
            foreach (var category in _categoryService.GetAllCategoriesByParentCategoryId(parentCategoryId: rootCategoryId, hideEmptyCategory: _catalogSettings.HideEmptyCategory))
            {
                if (validateIncludeInTopMenu && !category.IncludeInTopMenu)
                {
                    continue;
                }

                var categoryModel = new CategorySimpleModel()
                {
                    Id = category.Id,
                    Name = category.GetLocalized(x => x.Name),
                    SeName = category.GetSeName()
                };

                //product number for each category
                if (_catalogSettings.ShowCategoryProductNumber)
                {
                    categoryModel.NumberOfProducts = GetCategoryProductNumber(category.Id);
                }

                //load subcategories?
                bool loadSubCategories = false;
                if (loadSubCategoriesForIds == null)
                {
                    //load all subcategories
                    loadSubCategories = true;
                }
                else
                {
                    //we load subcategories only for certain categories
                    if (loadSubCategoriesForIds.Any(x => x == category.Id))
                    {
                        loadSubCategories = true;
                    }
                }
                if (levelsToLoad <= level)
                {
                    loadSubCategories = false;
                }
                if (loadSubCategories)
                {
                    var subCategories = PrepareCategorySimpleModels(category.Id, loadSubCategoriesForIds, level + 1, levelsToLoad, validateIncludeInTopMenu);
                    categoryModel.SubCategories.AddRange(subCategories);
                }
                result.Add(categoryModel);
            }

            return result;
        }

        protected IList<CategorySimpleModel> PrepareCategorySimpleModels(IList<Category> categories)
        {
            var result = new List<CategorySimpleModel>();
            foreach (var category in categories)
            {
                var categoryModel = new CategorySimpleModel()
                {
                    Id = category.Id,
                    Name = category.GetLocalized(x => x.Name),
                    SeName = category.GetSeName()
                };

                result.Add(categoryModel);
            }

            return result;
        }

        #endregion

        #region Manufacturers

        [NopHttpsRequirement(SslRequirement.No)]
        public ActionResult Manufacturer(int manufacturerId, CatalogPagingFilteringModel command)
        {
            // TODO: Manufacturer is the disabled feature.
            return RedirectToRoute("HomePage");
        }

        [NopHttpsRequirement(SslRequirement.No)]
        public ActionResult ManufacturerAll()
        {
            var model = new List<ManufacturerModel>();
            var manufacturers = _manufacturerService.GetAllManufacturers();
            foreach (var manufacturer in manufacturers)
            {
                var modelMan = manufacturer.ToModel();

                //prepare picture model
                int pictureSize = _mediaSettings.ManufacturerThumbPictureSize;
                var manufacturerPictureCacheKey = string.Format(ModelCacheEventConsumer.MANUFACTURER_PICTURE_MODEL_KEY, manufacturer.Id, pictureSize, true, _workContext.WorkingLanguage.Id, _webHelper.IsCurrentConnectionSecured());
                modelMan.PictureModel = _cacheManager.Get(manufacturerPictureCacheKey, () =>
                {
                    var pictureModel = new PictureModel()
                    {
                        FullSizeImageUrl = _pictureService.GetPictureUrl(manufacturer.PictureId),
                        ImageUrl = _pictureService.GetPictureUrl(manufacturer.PictureId, pictureSize),
                        Title = string.Format(_localizationService.GetResource("Media.Manufacturer.ImageLinkTitleFormat"), modelMan.Name),
                        AlternateText = string.Format(_localizationService.GetResource("Media.Manufacturer.ImageAlternateTextFormat"), modelMan.Name)
                    };
                    return pictureModel;
                });
                model.Add(modelMan);
            }

            return View(model);
        }

        public ActionResult MembershipInfo()
        {
            var memberships = _customerService.GetAllClubMemberships();
            var model = PrepareMembershipModel(memberships);
            return View(model);
        }

        [NonAction]
        private IList<ClubMembershipModel> PrepareMembershipModel(IList<ClubMembership> memberships)
        {
            if (memberships != null && memberships.Any())
            {
                return memberships.Select(m => new ClubMembershipModel()
                        {
                            Id = m.Id,
                            Name = m.Name,
                            Description = m.Description,
                            Duration = m.MembershipDuration,
                            ProductVariantId = m.ProductVariantId.HasValue ? m.ProductVariantId.Value : 0
                        }).ToList();
            }
            return null;
        }

        [ChildActionOnly]
        public ActionResult ManufacturerNavigation(int currentManufacturerId)
        {
            string cacheKey = string.Format(ModelCacheEventConsumer.MANUFACTURER_NAVIGATION_MODEL_KEY, currentManufacturerId, _workContext.WorkingLanguage.Id);
            var cacheModel = _cacheManager.Get(cacheKey, () =>
                {
                    var currentManufacturer = _manufacturerService.GetManufacturerById(currentManufacturerId);

                    var manufacturers = _manufacturerService.GetAllManufacturers();
                    var model = new ManufacturerNavigationModel()
                    {
                        TotalManufacturers = manufacturers.Count
                    };

                    foreach (var manufacturer in manufacturers.Take(_catalogSettings.ManufacturersBlockItemsToDisplay))
                    {
                        var modelMan = new ManufacturerBriefInfoModel()
                        {
                            Id = manufacturer.Id,
                            Name = manufacturer.GetLocalized(x => x.Name),
                            SeName = manufacturer.GetSeName(),
                            IsActive = currentManufacturer != null && currentManufacturer.Id == manufacturer.Id,
                        };
                        model.Manufacturers.Add(modelMan);
                    }
                    return model;
                });

            return PartialView(cacheModel);
        }

        #endregion

        #region Products

        //product details page
        [NopHttpsRequirement(SslRequirement.No)]
        public ActionResult Product(int productId)
        {
            var product = _productService.GetProductById(productId);
            if (product == null || product.Deleted)
                return RedirectToRoute("HomePage");

            //Is published?
            //Check whether the current user has a "Manage catalog" permission
            //It allows him to preview a product before publishing
            if (!product.Published && !_permissionService.Authorize(StandardPermissionProvider.ManageCatalog))
                return RedirectToRoute("HomePage");

            //ACL (access control list)
            if (!_aclService.Authorize(product))
                return RedirectToRoute("HomePage");

            //prepare the model
            var model = PrepareProductDetailsPageModel(product);

            //check whether we have at leat one variant
            if (model.ProductVariantModels.Count == 0)
                return RedirectToRoute("HomePage");

            //save as recently viewed
            _recentlyViewedProductsService.AddProductToRecentlyViewedList(product.Id);

            //activity log
            _customerActivityService.InsertActivity("PublicStore.ViewProduct", _localizationService.GetResource("ActivityLog.PublicStore.ViewProduct"), product.Name);

            RouteData.Values["storeId"] = product.StoreId;

            return View(model.ProductTemplateViewPath, model);
        }
        [HttpPost]
        [ValidateInput(false)]
        public ActionResult ReserveProductVariant(int productVariantId, int storeId, FormCollection form)
        {
            string redirectString = "";
            if (_workContext.CurrentCustomer == null || _workContext.CurrentCustomer.IsGuest())
            {
                redirectString = Url.RouteUrl("HomePage");
                return Json(new
                {
                    success = true,
                    redirect = redirectString
                });
            }
            var productVariant = _productService.GetProductVariantById(productVariantId);
            if (productVariant == null)
            {
                return Json(new
                {
                    redirect = Url.RouteUrl("HomePage"),
                });
            }

            #region Customer entered price
            decimal customerEnteredPriceConverted = decimal.Zero;
            if (productVariant.CustomerEntersPrice)
            {
                foreach (string formKey in form.AllKeys)
                {
                    if (formKey.Equals(string.Format("addtocart_{0}.CustomerEnteredPrice", productVariantId), StringComparison.InvariantCultureIgnoreCase))
                    {
                        decimal customerEnteredPrice = decimal.Zero;
                        if (decimal.TryParse(form[formKey], out customerEnteredPrice))
                            customerEnteredPriceConverted = _currencyService.ConvertToPrimaryStoreCurrency(customerEnteredPrice, _workContext.WorkingCurrency);
                        break;
                    }
                }
            }
            #endregion

            #region Quantity

            int quantity = 1;
            foreach (string formKey in form.AllKeys)
                if (formKey.Equals(string.Format("addtocart_{0}.EnteredQuantity", productVariantId), StringComparison.InvariantCultureIgnoreCase))
                {
                    int.TryParse(form[formKey], out quantity);
                    break;
                }

            #endregion

            var addToCartWarnings = new List<string>();
            string attributes = "";

            #region Product attributes
            string selectedAttributes = string.Empty;
            var productVariantAttributes = _productAttributeService.GetProductVariantAttributesByProductVariantId(productVariant.Id);
            foreach (var attribute in productVariantAttributes)
            {
                string controlId = string.Format("product_attribute_{0}_{1}_{2}", attribute.ProductVariantId, attribute.ProductAttributeId, attribute.Id);
                switch (attribute.AttributeControlType)
                {
                    case AttributeControlType.DropdownList:
                        {
                            var ddlAttributes = form[controlId];
                            if (!String.IsNullOrEmpty(ddlAttributes))
                            {
                                int selectedAttributeId = int.Parse(ddlAttributes);
                                if (selectedAttributeId > 0)
                                    selectedAttributes = _productAttributeParser.AddProductAttribute(selectedAttributes,
                                        attribute, selectedAttributeId.ToString());
                            }
                        }
                        break;
                    case AttributeControlType.RadioList:
                        {
                            var rblAttributes = form[controlId];
                            if (!String.IsNullOrEmpty(rblAttributes))
                            {
                                int selectedAttributeId = int.Parse(rblAttributes);
                                if (selectedAttributeId > 0)
                                    selectedAttributes = _productAttributeParser.AddProductAttribute(selectedAttributes,
                                        attribute, selectedAttributeId.ToString());
                            }
                        }
                        break;
                    case AttributeControlType.Checkboxes:
                        {
                            var cblAttributes = form[controlId];
                            if (!String.IsNullOrEmpty(cblAttributes))
                            {
                                foreach (var item in cblAttributes.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
                                {
                                    int selectedAttributeId = int.Parse(item);
                                    if (selectedAttributeId > 0)
                                        selectedAttributes = _productAttributeParser.AddProductAttribute(selectedAttributes,
                                            attribute, selectedAttributeId.ToString());
                                }
                            }
                        }
                        break;
                    case AttributeControlType.TextBox:
                        {
                            var txtAttribute = form[controlId];
                            if (!String.IsNullOrEmpty(txtAttribute))
                            {
                                string enteredText = txtAttribute.Trim();
                                selectedAttributes = _productAttributeParser.AddProductAttribute(selectedAttributes,
                                    attribute, enteredText);
                            }
                        }
                        break;
                    case AttributeControlType.MultilineTextbox:
                        {
                            var txtAttribute = form[controlId];
                            if (!String.IsNullOrEmpty(txtAttribute))
                            {
                                string enteredText = txtAttribute.Trim();
                                selectedAttributes = _productAttributeParser.AddProductAttribute(selectedAttributes,
                                    attribute, enteredText);
                            }
                        }
                        break;
                    case AttributeControlType.Datepicker:
                        {
                            var day = form[controlId + "_day"];
                            var month = form[controlId + "_month"];
                            var year = form[controlId + "_year"];
                            DateTime? selectedDate = null;
                            try
                            {
                                selectedDate = new DateTime(Int32.Parse(year), Int32.Parse(month), Int32.Parse(day));
                            }
                            catch { }
                            if (selectedDate.HasValue)
                            {
                                selectedAttributes = _productAttributeParser.AddProductAttribute(selectedAttributes,
                                    attribute, selectedDate.Value.ToString("D"));
                            }
                        }
                        break;
                    case AttributeControlType.FileUpload:
                        {
                            Guid downloadGuid;
                            Guid.TryParse(form[controlId], out downloadGuid);
                            var download = _downloadService.GetDownloadByGuid(downloadGuid);
                            if (download != null)
                            {
                                selectedAttributes = _productAttributeParser.AddProductAttribute(selectedAttributes,
                                        attribute, download.DownloadGuid.ToString());
                            }
                        }
                        break;
                    default:
                        break;
                }
            }
            attributes = selectedAttributes;

            #endregion

            #region Gift cards

            if (productVariant.IsGiftCard)
            {
                string recipientName = "";
                string recipientEmail = "";
                string senderName = "";
                string senderEmail = "";
                string giftCardMessage = "";
                foreach (string formKey in form.AllKeys)
                {
                    if (formKey.Equals(string.Format("giftcard_{0}.RecipientName", productVariantId), StringComparison.InvariantCultureIgnoreCase))
                    {
                        recipientName = form[formKey];
                        continue;
                    }
                    if (formKey.Equals(string.Format("giftcard_{0}.RecipientEmail", productVariantId), StringComparison.InvariantCultureIgnoreCase))
                    {
                        recipientEmail = form[formKey];
                        continue;
                    }
                    if (formKey.Equals(string.Format("giftcard_{0}.SenderName", productVariantId), StringComparison.InvariantCultureIgnoreCase))
                    {
                        senderName = form[formKey];
                        continue;
                    }
                    if (formKey.Equals(string.Format("giftcard_{0}.SenderEmail", productVariantId), StringComparison.InvariantCultureIgnoreCase))
                    {
                        senderEmail = form[formKey];
                        continue;
                    }
                    if (formKey.Equals(string.Format("giftcard_{0}.Message", productVariantId), StringComparison.InvariantCultureIgnoreCase))
                    {
                        giftCardMessage = form[formKey];
                        continue;
                    }
                }

                attributes = _productAttributeParser.AddGiftCardAttribute(attributes,
                    recipientName, recipientEmail, senderName, senderEmail, giftCardMessage);
            }

            #endregion

            //save item
            //var cartType = ShoppingCartType.ShoppingCart;
            //addToCartWarnings.AddRange(_shoppingCartService.AddToCart(_workContext.CurrentCustomer,
            //    productVariant, cartType, attributes, customerEnteredPriceConverted, quantity, true));
            _httpContext.Session["ReserveCar"] = new ReserveCarModel() { ProductVariantId = productVariantId, Quantity = quantity, Attributes = attributes, CustomerEnteredPrice = customerEnteredPriceConverted };
            #region Return result

            //if (addToCartWarnings.Count > 0)
            //{
            //    //cannot be added to the cart/wishlist
            //    //let's display warnings
            //    return Json(new
            //    {
            //        success = false,
            //        message = addToCartWarnings.ToArray()
            //    });
            //}
            //else
            //{
            redirectString = Url.RouteUrl("PreOrderOnePageCheckOut", new { StoreId = storeId });
            return Json(new
            {
                success = true,
                redirect = redirectString
            });
            #endregion
        }
        //add product variant to cart using HTTP POST
        //currently we use this method only for mobile device version
        //desktop version uses AJAX version of this method (ShoppingCartController.AddProductVariantToCart)
        [HttpPost, ActionName("Product")]
        [ValidateInput(false)]
        public ActionResult AddProductVariantToCart(int productId, FormCollection form)
        {
            var product = _productService.GetProductById(productId);
            if (product == null || product.Deleted || !product.Published)
                return RedirectToRoute("HomePage");

            //manually process form
            int productVariantId = 0;
            ShoppingCartType cartType = ShoppingCartType.ShoppingCart;
            foreach (string formKey in form.AllKeys)
            {
                if (formKey.StartsWith("addtocartbutton-"))
                {
                    productVariantId = Convert.ToInt32(formKey.Substring(("addtocartbutton-").Length));
                    cartType = ShoppingCartType.ShoppingCart;
                }
                else if (formKey.StartsWith("addtowishlistbutton-"))
                {
                    productVariantId = Convert.ToInt32(formKey.Substring(("addtowishlistbutton-").Length));
                    cartType = ShoppingCartType.Wishlist;
                }
            }

            var productVariant = _productService.GetProductVariantById(productVariantId);
            if (productVariant == null)
                return RedirectToRoute("HomePage");

            #region Tier Price

            /// TO-DO: Add Tier-Price to Cart			
            TierPrice currentTierPrice = null;
            if (productVariant.HasTierPrices)
            {
                var validPrices = productVariant.TierPrices.ToList()
                                                           .FilterForCustomer(_workContext.CurrentCustomer);
                if (validPrices != null && validPrices.Count > 0)
                {
                    var minPrice = validPrices.Min(ct => ct.Price);
                    if (minPrice > 0)
                        currentTierPrice = validPrices.FirstOrDefault(p => p.Price == minPrice);
                }
            }
            #endregion


            #region Customer entered price
            decimal customerEnteredPrice = decimal.Zero;
            decimal customerEnteredPriceConverted = decimal.Zero;
            if (productVariant.CustomerEntersPrice)
            {
                foreach (string formKey in form.AllKeys)
                    if (formKey.Equals(string.Format("addtocart_{0}.CustomerEnteredPrice", productVariantId), StringComparison.InvariantCultureIgnoreCase))
                    {
                        if (decimal.TryParse(form[formKey], out customerEnteredPrice))
                            customerEnteredPriceConverted = _currencyService.ConvertToPrimaryStoreCurrency(customerEnteredPrice, _workContext.WorkingCurrency);
                        break;
                    }
            }
            #endregion

            #region Quantity

            int quantity = 1;
            foreach (string formKey in form.AllKeys)
                if (formKey.Equals(string.Format("addtocart_{0}.EnteredQuantity", productVariantId), StringComparison.InvariantCultureIgnoreCase))
                {
                    int.TryParse(form[formKey], out quantity);
                    break;
                }

            #endregion

            var addToCartWarnings = new List<string>();
            string attributes = "";

            #region Product attributes
            string selectedAttributes = string.Empty;
            var productVariantAttributes = _productAttributeService.GetProductVariantAttributesByProductVariantId(productVariant.Id);
            foreach (var attribute in productVariantAttributes)
            {
                string controlId = string.Format("product_attribute_{0}_{1}_{2}", attribute.ProductVariantId, attribute.ProductAttributeId, attribute.Id);
                switch (attribute.AttributeControlType)
                {
                    case AttributeControlType.DropdownList:
                        {
                            var ddlAttributes = form[controlId];
                            if (!String.IsNullOrEmpty(ddlAttributes))
                            {
                                int selectedAttributeId = int.Parse(ddlAttributes);
                                if (selectedAttributeId > 0)
                                    selectedAttributes = _productAttributeParser.AddProductAttribute(selectedAttributes,
                                        attribute, selectedAttributeId.ToString());
                            }
                        }
                        break;
                    case AttributeControlType.RadioList:
                        {
                            var rblAttributes = form[controlId];
                            if (!String.IsNullOrEmpty(rblAttributes))
                            {
                                int selectedAttributeId = int.Parse(rblAttributes);
                                if (selectedAttributeId > 0)
                                    selectedAttributes = _productAttributeParser.AddProductAttribute(selectedAttributes,
                                        attribute, selectedAttributeId.ToString());
                            }
                        }
                        break;
                    case AttributeControlType.Checkboxes:
                        {
                            var cblAttributes = form[controlId];
                            if (!String.IsNullOrEmpty(cblAttributes))
                            {
                                foreach (var item in cblAttributes.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
                                {
                                    int selectedAttributeId = int.Parse(item);
                                    if (selectedAttributeId > 0)
                                        selectedAttributes = _productAttributeParser.AddProductAttribute(selectedAttributes,
                                            attribute, selectedAttributeId.ToString());
                                }
                            }
                        }
                        break;
                    case AttributeControlType.TextBox:
                        {
                            var txtAttribute = form[controlId];
                            if (!String.IsNullOrEmpty(txtAttribute))
                            {
                                string enteredText = txtAttribute.Trim();
                                selectedAttributes = _productAttributeParser.AddProductAttribute(selectedAttributes,
                                    attribute, enteredText);
                            }
                        }
                        break;
                    case AttributeControlType.MultilineTextbox:
                        {
                            var txtAttribute = form[controlId];
                            if (!String.IsNullOrEmpty(txtAttribute))
                            {
                                string enteredText = txtAttribute.Trim();
                                selectedAttributes = _productAttributeParser.AddProductAttribute(selectedAttributes,
                                    attribute, enteredText);
                            }
                        }
                        break;
                    case AttributeControlType.Datepicker:
                        {
                            var day = form[controlId + "_day"];
                            var month = form[controlId + "_month"];
                            var year = form[controlId + "_year"];
                            DateTime? selectedDate = null;
                            try
                            {
                                selectedDate = new DateTime(Int32.Parse(year), Int32.Parse(month), Int32.Parse(day));
                            }
                            catch { }
                            if (selectedDate.HasValue)
                            {
                                selectedAttributes = _productAttributeParser.AddProductAttribute(selectedAttributes,
                                    attribute, selectedDate.Value.ToString("D"));
                            }
                        }
                        break;
                    case AttributeControlType.FileUpload:
                        {
                            var httpPostedFile = this.Request.Files[controlId];
                            if ((httpPostedFile != null) && (!String.IsNullOrEmpty(httpPostedFile.FileName)))
                            {
                                int fileMaxSize = _catalogSettings.FileUploadMaximumSizeBytes;
                                if (httpPostedFile.ContentLength > fileMaxSize)
                                {
                                    addToCartWarnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.MaximumUploadedFileSize"), (int)(fileMaxSize / 1024)));
                                }
                                else
                                {
                                    //save an uploaded file
                                    var download = new Download()
                                    {
                                        DownloadGuid = Guid.NewGuid(),
                                        UseDownloadUrl = false,
                                        DownloadUrl = "",
                                        DownloadBinary = httpPostedFile.GetDownloadBits(),
                                        ContentType = httpPostedFile.ContentType,
                                        Filename = System.IO.Path.GetFileNameWithoutExtension(httpPostedFile.FileName),
                                        Extension = System.IO.Path.GetExtension(httpPostedFile.FileName),
                                        IsNew = true
                                    };
                                    _downloadService.InsertDownload(download);
                                    //save attribute
                                    selectedAttributes = _productAttributeParser.AddProductAttribute(selectedAttributes,
                                        attribute, download.DownloadGuid.ToString());
                                }
                            }
                        }
                        break;
                    default:
                        break;
                }
            }
            attributes = selectedAttributes;

            #endregion

            #region Gift cards

            string recipientName = "";
            string recipientEmail = "";
            string senderName = "";
            string senderEmail = "";
            string giftCardMessage = "";
            if (productVariant.IsGiftCard)
            {
                foreach (string formKey in form.AllKeys)
                {
                    if (formKey.Equals(string.Format("giftcard_{0}.RecipientName", productVariantId), StringComparison.InvariantCultureIgnoreCase))
                    {
                        recipientName = form[formKey];
                        continue;
                    }
                    if (formKey.Equals(string.Format("giftcard_{0}.RecipientEmail", productVariantId), StringComparison.InvariantCultureIgnoreCase))
                    {
                        recipientEmail = form[formKey];
                        continue;
                    }
                    if (formKey.Equals(string.Format("giftcard_{0}.SenderName", productVariantId), StringComparison.InvariantCultureIgnoreCase))
                    {
                        senderName = form[formKey];
                        continue;
                    }
                    if (formKey.Equals(string.Format("giftcard_{0}.SenderEmail", productVariantId), StringComparison.InvariantCultureIgnoreCase))
                    {
                        senderEmail = form[formKey];
                        continue;
                    }
                    if (formKey.Equals(string.Format("giftcard_{0}.Message", productVariantId), StringComparison.InvariantCultureIgnoreCase))
                    {
                        giftCardMessage = form[formKey];
                        continue;
                    }
                }

                attributes = _productAttributeParser.AddGiftCardAttribute(attributes,
                    recipientName, recipientEmail, senderName, senderEmail, giftCardMessage);
            }

            #endregion

            //save item
            addToCartWarnings.AddRange(_shoppingCartService.AddToCart(_workContext.CurrentCustomer,
                productVariant, cartType, attributes, customerEnteredPriceConverted, quantity, true));

            #region Set already entered values

            //set already entered values (quantity, customer entered price, gift card attributes, product attributes
            //we do it manually because views do not use HTML helpers for rendering controls

            Action<ProductDetailsModel> setEnteredValues = (productModel) =>
                {
                    //find product variant model
                    var productVariantModel = productModel
                        .ProductVariantModels
                        .Where(x => x.Id == productVariant.Id)
                        .FirstOrDefault();
                    if (productVariantModel == null)
                        return;

                    #region 'Add to cart' model

                    //entered quantity
                    productVariantModel.AddToCart.EnteredQuantity = quantity;
                    //allowed quantities
                    var allowedQuantities = productVariant.ParseAllowedQuatities();
                    if (allowedQuantities.Length > 0)
                    {
                        var allowedQuantitySelectedItem = productVariantModel.AddToCart.AllowedQuantities
                            .Where(x => x.Text == quantity.ToString())
                            .FirstOrDefault();
                        if (allowedQuantitySelectedItem != null)
                        {
                            allowedQuantitySelectedItem.Selected = true;
                        }
                    }

                    //customer entered price
                    if (productVariantModel.AddToCart.CustomerEntersPrice)
                    {
                        productVariantModel.AddToCart.CustomerEnteredPrice = customerEnteredPrice;
                    }

                    #endregion

                    #region Gift card attributes

                    if (productVariant.IsGiftCard)
                    {
                        productVariantModel.GiftCard.RecipientName = recipientName;
                        productVariantModel.GiftCard.RecipientEmail = recipientEmail;
                        productVariantModel.GiftCard.SenderName = senderName;
                        productVariantModel.GiftCard.SenderEmail = senderEmail;
                        productVariantModel.GiftCard.Message = giftCardMessage;
                    }

                    #endregion

                    #region Product attributes
                    //clear pre-defined values)
                    foreach (var pvaModel in productVariantModel.ProductVariantAttributes)
                    {
                        foreach (var pvavModel in pvaModel.Values)
                            pvavModel.IsPreSelected = false;
                    }
                    //select the previously entered ones
                    foreach (var attribute in productVariantAttributes)
                    {
                        string controlId = string.Format("product_attribute_{0}_{1}_{2}", attribute.ProductVariantId, attribute.ProductAttributeId, attribute.Id);
                        switch (attribute.AttributeControlType)
                        {
                            case AttributeControlType.DropdownList:
                                {
                                    var ddlAttributes = form[controlId];
                                    if (!String.IsNullOrEmpty(ddlAttributes))
                                    {
                                        int selectedAttributeId = int.Parse(ddlAttributes);
                                        if (selectedAttributeId > 0)
                                        {
                                            var pvavModel = productVariantModel.ProductVariantAttributes
                                                .SelectMany(x => x.Values)
                                                .Where(y => y.Id == selectedAttributeId)
                                                .FirstOrDefault();
                                            if (pvavModel != null)
                                                pvavModel.IsPreSelected = true;
                                        }
                                    }
                                }
                                break;
                            case AttributeControlType.RadioList:
                                {
                                    var rblAttributes = form[controlId];
                                    if (!String.IsNullOrEmpty(rblAttributes))
                                    {
                                        int selectedAttributeId = int.Parse(rblAttributes);
                                        if (selectedAttributeId > 0)
                                        {
                                            var pvavModel = productVariantModel.ProductVariantAttributes
                                                .SelectMany(x => x.Values)
                                                .Where(y => y.Id == selectedAttributeId)
                                                .FirstOrDefault();
                                            if (pvavModel != null)
                                                pvavModel.IsPreSelected = true;
                                        }
                                    }
                                }
                                break;
                            case AttributeControlType.Checkboxes:
                                {
                                    var cblAttributes = form[controlId];
                                    if (!String.IsNullOrEmpty(cblAttributes))
                                    {
                                        foreach (var item in cblAttributes.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
                                        {
                                            int selectedAttributeId = int.Parse(item);
                                            if (selectedAttributeId > 0)
                                            {
                                                var pvavModel = productVariantModel.ProductVariantAttributes
                                                   .SelectMany(x => x.Values)
                                                   .Where(y => y.Id == selectedAttributeId)
                                                   .FirstOrDefault();
                                                if (pvavModel != null)
                                                    pvavModel.IsPreSelected = true;
                                            }
                                        }
                                    }
                                }
                                break;
                            case AttributeControlType.TextBox:
                                {
                                    var txtAttribute = form[controlId];
                                    if (!String.IsNullOrEmpty(txtAttribute))
                                    {
                                        var pvaModel = productVariantModel
                                            .ProductVariantAttributes
                                            .Select(x => x)
                                            .Where(y => y.Id == attribute.Id)
                                            .FirstOrDefault();

                                        if (pvaModel != null)
                                            pvaModel.TextValue = txtAttribute;
                                    }
                                }
                                break;
                            case AttributeControlType.MultilineTextbox:
                                {
                                    var txtAttribute = form[controlId];
                                    if (!String.IsNullOrEmpty(txtAttribute))
                                    {
                                        var pvaModel = productVariantModel
                                            .ProductVariantAttributes
                                            .Select(x => x)
                                            .Where(y => y.Id == attribute.Id)
                                            .FirstOrDefault();

                                        if (pvaModel != null)
                                            pvaModel.TextValue = txtAttribute;
                                    }
                                }
                                break;
                            case AttributeControlType.Datepicker:
                                {
                                    var pvaModel = productVariantModel
                                        .ProductVariantAttributes
                                        .Select(x => x)
                                        .Where(y => y.Id == attribute.Id)
                                        .FirstOrDefault();
                                    if (pvaModel != null)
                                    {
                                        int day, month, year;
                                        if (int.TryParse(form[controlId + "_day"], out day))
                                            pvaModel.SelectedDay = day;
                                        if (int.TryParse(form[controlId + "_month"], out month))
                                            pvaModel.SelectedMonth = month;
                                        if (int.TryParse(form[controlId + "_year"], out year))
                                            pvaModel.SelectedYear = year;
                                    }
                                }
                                break;
                            default:
                                break;
                        }
                    }

                    #endregion
                };

            #endregion

            #region Return the view

            if (addToCartWarnings.Count == 0)
            {
                switch (cartType)
                {
                    case ShoppingCartType.Wishlist:
                        {
                            if (_shoppingCartSettings.DisplayWishlistAfterAddingProduct)
                            {
                                //redirect to the wishlist page
                                return RedirectToRoute("Wishlist");
                            }
                            else
                            {
                                //redisplay the page with "Product has been added to the wishlist" notification message
                                var model = PrepareProductDetailsPageModel(product);
                                this.SuccessNotification(_localizationService.GetResource("Products.ProductHasBeenAddedToTheWishlist"), false);
                                //set already entered values (quantity, customer entered price, gift card attributes, product attributes)
                                setEnteredValues(model);

                                //activity log
                                _customerActivityService.InsertActivity("PublicStore.AddToWishlist", _localizationService.GetResource("ActivityLog.PublicStore.AddToWishlist"), productVariant.FullProductName);

                                return View(model.ProductTemplateViewPath, model);
                            }
                        }
                    case ShoppingCartType.ShoppingCart:
                    default:
                        {
                            if (_shoppingCartSettings.DisplayCartAfterAddingProduct)
                            {
                                //redirect to the shopping cart page
                                return RedirectToRoute("ShoppingCart");
                            }
                            else
                            {
                                //redisplay the page with "Product has been added to the cart" notification message
                                var model = PrepareProductDetailsPageModel(product);
                                this.SuccessNotification(_localizationService.GetResource("Products.ProductHasBeenAddedToTheCart"), false);
                                //set already entered values (quantity, customer entered price, gift card attributes, product attributes)
                                setEnteredValues(model);

                                //activity log
                                _customerActivityService.InsertActivity("PublicStore.AddToShoppingCart", _localizationService.GetResource("ActivityLog.PublicStore.AddToShoppingCart"), productVariant.FullProductName);

                                return View(model.ProductTemplateViewPath, model);
                            }
                        }
                }
            }
            else
            {
                //Errors
                foreach (string error in addToCartWarnings)
                    ModelState.AddModelError("", error);

                //If we got this far, something failed, redisplay form
                var model = PrepareProductDetailsPageModel(product);
                //set already entered values (quantity, customer entered price, gift card attributes, product attributes
                setEnteredValues(model);
                return View(model.ProductTemplateViewPath, model);
            }

            #endregion
        }

        [HttpPost]
        public ActionResult LoadOpenBoxItems(int productVariantId)
        {
            var productVariant = _productService.GetProductVariantById(productVariantId);
            var product = _productService.GetProductById(productVariant.ProductId);
            var backupPicture = new PictureModel();
            if (product.ProductPictures.Count > 0)
            {
                var picture = product.ProductPictures.First();
                backupPicture = new PictureModel()
                {
                    ImageUrl = _pictureService.GetPictureUrl(picture.PictureId, _mediaSettings.StoreLogoProductBoxPictureSize),
                    FullSizeImageUrl = _pictureService.GetPictureUrl(picture.PictureId),
                    Title = string.Format(_localizationService.GetResource("Media.Product.ImageLinkTitleFormat"), product.Name),
                    AlternateText = string.Format(_localizationService.GetResource("Media.Product.ImageAlternateTextFormat"), product.Name)
                };
            }
            var pvs = _productService.GetProductVariantsByProductId(productVariant.ProductId, GetRegionId()).Where(pv => pv.BoundProductVariantId == productVariant.Id && pv.IsOpenBoxProductVariant && pv.StockQuantity > 0);
            if (pvs.Count() == 0)
            {
                WarningNotification("There is no open-box item available for sale for this product variant.");
            }
            var modelList = new List<ProductDetailsModel.ProductVariantModel>();
            foreach (var pv in pvs)
            {
                var model = PrepareProductVariantModel(new ProductDetailsModel.ProductVariantModel(), pv);
                if (model.PictureModel == null || String.IsNullOrEmpty(model.PictureModel.ImageUrl))
                {
                    model.PictureModel = backupPicture;
                }
                modelList.Add(model);
            }
            return Json(new
            {
                html = this.RenderPartialViewToString("LoadOpenBoxItems", modelList),
                messgae = RenderAjaxNotification()
            });
        }

        [ChildActionOnly]
        public ActionResult ProductBreadcrumb(int productId)
        {
            var product = _productService.GetProductById(productId);
            if (product == null)
                throw new ArgumentException("No product found with the specified id");

            if (!_catalogSettings.CategoryBreadcrumbEnabled)
                return Content("");

            var customerRolesIds = _workContext.CurrentCustomer.CustomerRoleMappings
                .Where(cr => cr.CustomerRole.Active).Select(cr => cr.Id).ToList();
            var cacheKey = string.Format(ModelCacheEventConsumer.PRODUCT_BREADCRUMB_MODEL_KEY, product.Id, _workContext.WorkingLanguage.Id, string.Join(",", customerRolesIds));
            var cacheModel = _cacheManager.Get(cacheKey, () =>
            {
                var model = new ProductDetailsModel.ProductBreadcrumbModel()
                {
                    ProductId = product.Id,
                    ProductName = product.GetLocalized(x => x.Name),
                    ProductSeName = product.GetSeName()
                };
                var productCategories = _categoryService.GetProductCategoriesByProductId(product.Id);
                if (productCategories.Count > 0)
                {
                    var category = productCategories[0].Category;
                    if (category != null)
                    {
                        foreach (var catBr in GetCategoryBreadCrumb(category))
                        {
                            model.CategoryBreadcrumb.Add(new CategoryModel()
                            {
                                Id = catBr.Id,
                                Name = catBr.GetLocalized(x => x.Name),
                                SeName = catBr.GetSeName()
                            });
                        }
                    }
                }
                return model;
            });

            return PartialView(cacheModel);
        }

        [ChildActionOnly]
        public ActionResult ProductManufacturers(int productId)
        {
            string cacheKey = string.Format(ModelCacheEventConsumer.PRODUCT_MANUFACTURERS_MODEL_KEY, productId, _workContext.WorkingLanguage.Id);
            var cacheModel = _cacheManager.Get(cacheKey, () =>
            {
                var model = _manufacturerService.GetProductManufacturersByProductId(productId)
                    .Select(x =>
                    {
                        var m = x.Manufacturer.ToModel();
                        return m;
                    })
                    .ToList();
                return model;
            });

            return PartialView(cacheModel);
        }

        [ChildActionOnly]
        public ActionResult ProductReviewOverview(int productId)
        {
            var product = _productService.GetProductById(productId);
            if (product == null)
                throw new ArgumentException("No product found with the specified id");

            var model = new ProductReviewOverviewModel()
            {
                ProductId = product.Id,
                RatingSum = product.ApprovedRatingSum,
                TotalReviews = product.ApprovedTotalReviews,
                AllowCustomerReviews = product.AllowCustomerReviews && ProductReviewEnabledForCustomer(_workContext.CurrentCustomer, product)
            };
            return PartialView(model);
        }

        [ChildActionOnly]
        public ActionResult ProductReviewCount(int productId)
        {
            var product = _productService.GetProductById(productId);
            if (product == null)
                throw new ArgumentException("No product found with the specified id");

            var model = new ProductReviewOverviewModel()
            {
                ProductId = product.Id,
                RatingSum = product.ApprovedRatingSum,
                TotalReviews = product.ApprovedTotalReviews,
                AllowCustomerReviews = product.AllowCustomerReviews && ProductReviewEnabledForCustomer(_workContext.CurrentCustomer, product)
            };
            return PartialView(model);
        }

        [ChildActionOnly]
        //[OutputCache(Duration = 120, VaryByCustom = "WorkingLanguage")]
        public ActionResult ProductSpecifications(int productId)
        {
            var product = _productService.GetProductById(productId);
            if (product == null)
                throw new ArgumentException("No product found with the specified id");

            var model = PrepareProductSpecificationModel(product);
            return PartialView(model);
        }

        [ChildActionOnly]
        public ActionResult ProductTierPrices(int productVariantId)
        {
            if (!_permissionService.Authorize(StandardPermissionProvider.DisplayPrices))
                return Content(""); //hide prices

            var variant = _productService.GetProductVariantById(productVariantId);
            if (variant == null)
                throw new ArgumentException("No product variant found with the specified id");

            var baseVariantPrice = _currencyService.ConvertFromPrimaryStoreCurrency(variant.Price, _workContext.WorkingCurrency);

            var model = new List<ProductDetailsModel.ProductVariantModel.TierPriceModel>();

            var regularTier = new ProductDetailsModel.ProductVariantModel.TierPriceModel()
                                {
                                    Price = _priceFormatter.FormatPrice(variant.Price, true, false),
                                    SavedAmount = "--",
                                    Quantity = variant.StockQuantity,
                                    TierSystemName = "Regular Price",
                                    DisplayOrder = model.Count == 0 ? 1 : model.Max(m => m.DisplayOrder) + 1
                                };
            ViewBag.RegularPrice = regularTier;
            ViewBag.CurrentTierPrice = regularTier;

            if (variant.HasTierPrices)
            {
                var tierPrices = variant.TierPrices
                                .Where(x => x.CustomerRoleId != 3 && x.Quantity == 1)
                                .OrderBy(x => x.Price)
                                .ToList();
                model = tierPrices
                             .Select((tierPrice, index) =>
                                {
                                    var m = CreateTierPriceModel(variant, baseVariantPrice, tierPrice, index);
                                    return m;
                                })
                    .ToList();

                if (!_workContext.CurrentCustomer.IsGuest() && tierPrices.Any())
                {
                    tierPrices = tierPrices.FilterForCustomer(_workContext.CurrentCustomer).ToList();
                    if (tierPrices.Count > 0)
                    {
                        var minPrice = tierPrices.Min(ct => ct.Price);
                        ViewBag.CurrentTierPrice = CreateTierPriceModel(variant, baseVariantPrice, tierPrices.FirstOrDefault(ct => ct.Price == minPrice));
                    }
                }
            }

            return PartialView(model);
        }

        private ProductDetailsModel.ProductVariantModel.TierPriceModel CreateTierPriceModel(ProductVariant variant, decimal baseVariantPrice, TierPrice tierPrice, int index)
        {
            var m = CreateTierPriceModel(variant, baseVariantPrice, tierPrice);
            m.DisplayOrder = index;
            return m;
        }

        private ProductDetailsModel.ProductVariantModel.TierPriceModel CreateTierPriceModel(ProductVariant variant, decimal baseVariantPrice, TierPrice tierPrice)
        {
            var m = new ProductDetailsModel.ProductVariantModel.TierPriceModel()
            {
                Quantity = tierPrice.Quantity
            };
            List<TaxSurcharge> taxRate;
            decimal priceBase = _taxService.GetProductPrice(variant, tierPrice.Price, false, _workContext.CurrentCustomer, out taxRate);
            //_taxService.GetProductPrice(variant, tierPrice.Price, out taxRate);
            decimal price = _currencyService.ConvertFromPrimaryStoreCurrency(priceBase, _workContext.WorkingCurrency);
            m.Price = _priceFormatter.FormatPrice(price, true, false);
            m.SavedAmount = _priceFormatter.FormatPrice(baseVariantPrice - price, true, false);
            var customerRoleMapping = _workContext.CurrentCustomer.CustomerRoleMappings.FirstOrDefault(crm => crm.CustomerRoleId == tierPrice.CustomerRoleId.Value);
            if (customerRoleMapping != null)
                m.TierSystemName = GetTierName(customerRoleMapping.CustomerRole);
            m.CustomerRoleId = tierPrice.CustomerRoleId.HasValue ? tierPrice.CustomerRoleId.Value : 0;
            return m;
        }

        [NonAction]
        private string GetTierName(CustomerRole role)
        {
            return role.SystemName;
        }

        [ChildActionOnly]
        public ActionResult RelatedProducts(int productId, int? productThumbPictureSize)
        {
            var products = new List<Product>();
            var relatedProducts = _productService
                .GetRelatedProductsByProductId1(productId, GetRegionId());
            foreach (var product in _productService.GetProductsByIds(relatedProducts.Select(x => x.ProductId2).ToArray(), GetRegionId()))
            {
                var variants = _productService.GetProductVariantsByProductId(product.Id, GetRegionId());
                //ensure that a product has at least one available variant
                //and has ACL permission
                if (variants.Count > 0 && _aclService.Authorize(product))
                    products.Add(product);
            }
            var model = PrepareProductOverviewModels(products, productThumbPictureSize: productThumbPictureSize).ToList();

            return PartialView(model);
        }

        [ChildActionOnly]
        public ActionResult ProductsAlsoPurchased(int productId, int? productThumbPictureSize)
        {
            if (!_catalogSettings.ProductsAlsoPurchasedEnabled)
                return Content("");

            //load and cache report
            var productIds = _cacheManager.Get(string.Format(ModelCacheEventConsumer.PRODUCTS_ALSO_PURCHASED_IDS_KEY, productId),
                () =>
                    _orderReportService
                    .GetProductsAlsoPurchasedById(productId, _catalogSettings.ProductsAlsoPurchasedNumber)
                    .Select(x => x.Id)
                    .ToArray()
                    );

            //load products
            var products = _productService.GetProductsByIds(productIds, 0);
            //ACL
            products = products.Where(p => _aclService.Authorize(p)).ToList();
            IEnumerableExtension.Shuffle(products);
            //prepare model
            var model = PrepareProductOverviewModels(products, productThumbPictureSize: productThumbPictureSize).ToList();

            ViewData["IsLogin"] = _workContext.CurrentCustomer.IsGuest();

            return PartialView(model);
        }

        [ChildActionOnly]
        public ActionResult ShareButton()
        {
            if (_catalogSettings.ShowShareButton && !String.IsNullOrEmpty(_catalogSettings.PageShareCode))
            {
                var shareCode = _catalogSettings.PageShareCode;
                if (_webHelper.IsCurrentConnectionSecured())
                {
                    //need to change the addthis link to be https linked when the page is, so that the page doesnt ask about mixed mode when viewed in https...
                    shareCode = shareCode.Replace("http://", "https://");
                }

                return PartialView("ShareButton", shareCode);
            }

            return Content("");
        }

        [ChildActionOnly]
        public ActionResult CrossSellProducts(int? productThumbPictureSize)
        {
            var cart = _workContext.CurrentCustomer.ShoppingCartItems.Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart).ToList();
            var products = _productService.GetCrosssellProductsByShoppingCart(cart, GetRegionId(), _shoppingCartSettings.CrossSellsNumber);
            //ACL
            products = products.Where(p => _aclService.Authorize(p)).ToList();
            IEnumerableExtension.Shuffle(products);

            //Cross-sell products are dispalyed on the shopping cart page.
            //We know that the entire shopping cart page is not refresh
            //even if "ShoppingCartSettings.DisplayCartAfterAddingProduct" setting  is enabled.
            //That's why we force page refresh (redirect) in this case
            var model = PrepareProductOverviewModels(products,
                productThumbPictureSize: productThumbPictureSize, forceRedirectionAfterAddingToCart: true)
                .ToList();

            ViewData["IsLogin"] = _workContext.CurrentCustomer.IsGuest();

            return PartialView(model);
        }

        //recently viewed products
        [NopHttpsRequirement(SslRequirement.No)]
        public ActionResult RecentlyViewedProducts()
        {
            var model = new List<ProductOverviewModel>();
            if (_catalogSettings.RecentlyViewedProductsEnabled)
            {
                var products = _recentlyViewedProductsService.GetRecentlyViewedProducts(_catalogSettings.RecentlyViewedProductsNumber);
                model.AddRange(PrepareProductOverviewModels(products));
            }
            return View(model);
        }

        [ChildActionOnly]
        public ActionResult RecentlyViewedProductsBlock(int? productThumbPictureSize)
        {
            var model = new List<ProductOverviewModel>();
            if (_catalogSettings.RecentlyViewedProductsEnabled)
            {                
                var products = _recentlyViewedProductsService.GetRecentlyViewedProducts(_catalogSettings.RecentlyViewedProductsNumber);
                model.AddRange(PrepareProductOverviewModels(products, true, true, true, true, productThumbPictureSize));
            }
            return PartialView(model);
        }

        //recently added products
        [NopHttpsRequirement(SslRequirement.No)]
        public ActionResult RecentlyAddedProducts()
        {
            var model = new List<ProductOverviewModel>();
            if (_catalogSettings.RecentlyAddedProductsEnabled)
            {
                IList<int> filterableSpecificationAttributeOptionIds = null;
                var products = _productService.SearchProducts(GetRegionId(), 0, 0, null, null,
                    null, 0, null, false, false, _workContext.WorkingLanguage.Id,
                    null, ProductSortingEnum.CreatedOn, 0, _catalogSettings.RecentlyAddedProductsNumber,
                    false, out filterableSpecificationAttributeOptionIds);
                model.AddRange(PrepareProductOverviewModels(products));
            }
            return View(model);
        }

        public ActionResult RecentlyAddedProductsRss()
        {
            var feed = new SyndicationFeed(
                                    string.Format("{0}: Recently added products", _storeInformationSettings.StoreName),
                                    "Information about products",
                                    new Uri(_webHelper.GetStoreLocation(false)),
                                    "RecentlyAddedProductsRSS",
                                    DateTime.UtcNow);

            if (!_catalogSettings.RecentlyAddedProductsEnabled)
                return new RssActionResult() { Feed = feed };

            var items = new List<SyndicationItem>();
            IList<int> filterableSpecificationAttributeOptionIds = null;
            var products = _productService.SearchProducts(GetRegionId(), 0, 0, null, null,
                null, 0, null, false, false, _workContext.WorkingLanguage.Id,
                null, ProductSortingEnum.CreatedOn, 0, _catalogSettings.RecentlyAddedProductsNumber,
                false, out filterableSpecificationAttributeOptionIds);
            foreach (var product in products)
            {
                string productUrl = Url.RouteUrl("Product", new { SeName = product.GetSeName() }, "http");
                items.Add(new SyndicationItem(product.GetLocalized(x => x.Name), product.GetLocalized(x => x.ShortDescription), new Uri(productUrl), String.Format("RecentlyAddedProduct:{0}", product.Id), product.CreatedOnUtc));
            }
            feed.Items = items;
            return new RssActionResult() { Feed = feed };
        }

        [ChildActionOnly]
        //[OutputCache(VaryByParam = "*", Duration = 60)]
        public ActionResult HomepageBestSellers(int? productThumbPictureSize, int? pagenumber, int categoryId = 0, int storeId = 1)
        {
            if (!_catalogSettings.ShowBestsellersOnHomepage || _catalogSettings.NumberOfBestsellersOnHomepage == 0)
                return Content("");
            var roleIdString = String.Join(":", _workContext.CurrentCustomer.CustomerRoleMappings
                                .OrderBy(rm => rm.CustomerRoleId)
                                .Select(rm => rm.CustomerRoleId.ToString()));
            string cacheKey = String.Format(ModelCacheEventConsumer.HOMEPAGE_BESTSELLERS_IDS_KEY, storeId, categoryId, roleIdString);

            //load and cache report
            var model = _cacheManager.Get(cacheKey, 1200, () => 
                {
                    //group by products (not product variants)
                    var reportLines = _orderReportService
                    .BestSellersReport(null, null, null, null, null, 0, GetRegionId(), storeId, categoryId, _catalogSettings.NumberOfBestsellersOnHomepage, groupBy: 2);
                    var products = _productService.GetProductsByIds(reportLines.Select(x => x.EntityId).ToArray(), 0);
                    products = products.Where(p => _aclService.Authorize(p)).ToList();
                    return PreparePagedProducts(products, pagenumber, productThumbPictureSize);
                });
                        
            return PartialView(model);
        }

        public ActionResult HomepageWeeklyNewProducts(int? productThumbPictureSize, int? pagenumber, int categoryId = 0, int storeId = 1)
        {
            // TODO: Want this show/no-show customizable via _catalogSettings in Admin Settings?
            // FORNOW: Borrowing _catalogSettings.RecentlyAddedProductsNumber to use here since seems to signify
            //         similar things!
            var regionId = GetRegionId();
            if (regionId <= 0)
                return PartialView(PreparePagedProducts(new List<Product>(), pagenumber, productThumbPictureSize));

            string cacheKey = String.Format(ModelCacheEventConsumer.HOMEPAGE_WEEKLYNEWPRODUCTS_IDS_KEY, regionId, storeId, categoryId);

            //load and cache report
            var products = _cacheManager.Get(cacheKey,
                () =>
                    _orderReportService
                    .WeeklyNewProductsReport(regionId, storeId, categoryId, _catalogSettings.RecentlyAddedProductsNumber));

            //ACL
            products = products.Where(p => _aclService.Authorize(p)).ToList();

            return PartialView(PreparePagedProducts(products, pagenumber, productThumbPictureSize));
        }

		//[OutputCache(VaryByParam = "*", Duration = 600)]
        public ActionResult HomepageProducts(int? productThumbPictureSize, int? pagenumber)
        {
            int region = GetRegionId();
            region = region == 0 ? 1 : region;
            var roleIdString = String.Join(":", _workContext.CurrentCustomer.CustomerRoleMappings
                               .OrderBy(rm => rm.CustomerRoleId)
                               .Select(rm => rm.CustomerRoleId.ToString()));
            var cacheKey = String.Format(ModelCacheEventConsumer.HOMEPAGE_FEATUREDPRODUCTS_IDS_KEY,
                                        region, pagenumber.HasValue ? pagenumber.Value: 0, 
                                        productThumbPictureSize.HasValue ? productThumbPictureSize.Value: 0, 
                                        roleIdString);
            var model = _cacheManager.Get(cacheKey, 1200, () => {
                var products = _productService.GetAllProductsDisplayedOnHomePage(region);
                //ACL
                products = products.Where(p => _aclService.Authorize(p) && p.Published && p.Store.Published).ToList();
                IEnumerableExtension.Shuffle(products);
                return PreparePagedProducts(products, pagenumber, productThumbPictureSize);
            });
            
            return PartialView(model);
        }

        public ActionResult BackInStockSubscribePopup(int productVariantId)
        {
            var variant = _productService.GetProductVariantById(productVariantId);
            if (variant == null || variant.Deleted)
                throw new ArgumentException("No product variant found with the specified id");

            var product = variant.Product;

            var model = new BackInStockSubscribeModel();
            model.ProductId = product.Id;
            model.ProductName = product.GetLocalized(x => x.Name);
            model.ProductSeName = product.GetSeName();
            model.ProductVariantId = variant.Id;
            model.IsCurrentCustomerRegistered = _workContext.CurrentCustomer.IsRegistered();
            model.MaximumBackInStockSubscriptions = _catalogSettings.MaximumBackInStockSubscriptions;
            model.CurrentNumberOfBackInStockSubscriptions = _backInStockSubscriptionService.GetAllSubscriptionsByCustomerId(_workContext.CurrentCustomer.Id, 0, 1).TotalCount;
            if (variant.ManageInventoryMethod == ManageInventoryMethod.ManageStock &&
                variant.BackorderMode == BackorderMode.NoBackorders &&
                variant.AllowBackInStockSubscriptions &&
                variant.StockQuantity <= 0)
            {
                //out of stock
                model.SubscriptionAllowed = true;
                model.AlreadySubscribed = _backInStockSubscriptionService.FindSubscription(_workContext.CurrentCustomer.Id, variant.Id) != null;
            }
            return View(model);
        }

        [HttpPost, ActionName("BackInStockSubscribePopup")]
        public ActionResult BackInStockSubscribePopupPOST(int productVariantId)
        {
            var variant = _productService.GetProductVariantById(productVariantId);
            if (variant == null || variant.Deleted)
                throw new ArgumentException("No product variant found with the specified id");

            if (!_workContext.CurrentCustomer.IsRegistered())
                return Content(_localizationService.GetResource("BackInStockSubscriptions.OnlyRegistered"));

            if (variant.ManageInventoryMethod == ManageInventoryMethod.ManageStock &&
                variant.BackorderMode == BackorderMode.NoBackorders &&
                variant.AllowBackInStockSubscriptions &&
                variant.StockQuantity <= 0)
            {
                //out of stock
                var subscription = _backInStockSubscriptionService.FindSubscription(_workContext.CurrentCustomer.Id,
                                                                                    variant.Id);
                if (subscription != null)
                {
                    //unsubscribe
                    _backInStockSubscriptionService.DeleteSubscription(subscription);
                    return Content("Unsubscribed");
                }
                else
                {
                    if (_backInStockSubscriptionService.GetAllSubscriptionsByCustomerId(_workContext.CurrentCustomer.Id, 0, 1).TotalCount >= _catalogSettings.MaximumBackInStockSubscriptions)
                        return Content(string.Format(_localizationService.GetResource("BackInStockSubscriptions.MaxSubscriptions"), _catalogSettings.MaximumBackInStockSubscriptions));

                    //subscribe   
                    subscription = new BackInStockSubscription()
                    {
                        Customer = _workContext.CurrentCustomer,
                        ProductVariant = variant,
                        CreatedOnUtc = DateTime.UtcNow
                    };
                    _backInStockSubscriptionService.InsertSubscription(subscription);
                    return Content("Subscribed");
                }

            }
            else
            {
                return Content(_localizationService.GetResource("BackInStockSubscriptions.NotAllowed"));
            }
        }

        public ActionResult ProductAll(int? productThumbPictureSize, int categoryId = 0, int storeId = 1)
        {
            var customerRegionId = GetRegionId();
            string cacheKey = String.Format(ModelCacheEventConsumer.PRODUCT_ALL_IDS_KEY, customerRegionId, storeId, categoryId);

            //load and cache report
            var products = _cacheManager.Get(cacheKey,
                () =>
                    _productService.GetAllProducts(customerRegionId, storeId: storeId, categoryId: categoryId));

            //ACL
            products = products.Where(p => _aclService.Authorize(p)).ToList();

            //prepare model
            var model = new HomePageProductsModel()
            {
                Products = PrepareProductOverviewModels(products, productThumbPictureSize: productThumbPictureSize).ToList()
            };

            return View(model);
        }

        [ChildActionOnly]
        public ActionResult BuyNowButton(int productVariantId)
        {
            var model = new BuyNowModel()
            {
                ProductVariantId = productVariantId
            };

            return PartialView(model);
        }

        #endregion

        #region Product tags

        //Product tags
        [ChildActionOnly]
        public ActionResult ProductTags(int productId)
        {
            var product = _productService.GetProductById(productId);
            if (product == null)
                throw new ArgumentException("No product found with the specified id");

            var cacheKey = string.Format(ModelCacheEventConsumer.PRODUCTTAG_BY_PRODUCT_MODEL_KEY, product.Id, _workContext.WorkingLanguage.Id);
            var cacheModel = _cacheManager.Get(cacheKey, () =>
                {
                    var model = product.ProductTags
                        .OrderByDescending(x => x.ProductCount)
                        .Select(x =>
                                    {
                                        var ptModel = new ProductTagModel()
                                        {
                                            Id = x.Id,
                                            Name = x.GetLocalized(y => y.Name),
                                            SeName = x.GetSeName(),
                                            ProductCount = x.ProductCount
                                        };
                                        return ptModel;
                                    })
                        .ToList();
                    return model;
                });

            return PartialView(cacheModel);
        }

        [ChildActionOnly]
        public ActionResult PopularProductTags()
        {
            var cacheKey = string.Format(ModelCacheEventConsumer.PRODUCTTAG_POPULAR_MODEL_KEY, _workContext.WorkingLanguage.Id);
            var cacheModel = _cacheManager.Get(cacheKey, () =>
            {
                var model = new PopularProductTagsModel();

                //get all tags
                var allTags = _productTagService.GetAllProductTags();
                var tags = allTags
                    .OrderByDescending(x => x.ProductCount)
                    .Where(x => x.ProductCount > 0)
                    .Take(_catalogSettings.NumberOfProductTags)
                    .ToList();
                //sorting
                tags = tags.OrderBy(x => x.GetLocalized(y => y.Name)).ToList();

                model.TotalTags = allTags.Count;

                foreach (var tag in tags)
                    model.Tags.Add(new ProductTagModel()
                    {
                        Id = tag.Id,
                        Name = tag.GetLocalized(y => y.Name),
                        SeName = tag.GetSeName(),
                        ProductCount = tag.ProductCount
                    });
                return model;
            });

            return PartialView(cacheModel);
        }

        public ActionResult PopularProductTagsFront()
        {
            var cacheKey = string.Format(ModelCacheEventConsumer.PRODUCTTAG_POPULAR_MODEL_KEY, _workContext.WorkingLanguage.Id);
            var cacheModel = _cacheManager.Get(cacheKey, () =>
            {
                var model = new PopularProductTagsModel();

                //get all tags
                var allTags = _productTagService.GetAllProductTags();
                var tags = allTags
                    .OrderByDescending(x => x.ProductCount)
                    .Where(x => x.ProductCount > 0)
                    .Take(_catalogSettings.NumberOfProductTags)
                    .ToList();
                //sorting
                tags = tags.OrderBy(x => x.GetLocalized(y => y.Name)).ToList();

                model.TotalTags = allTags.Count;

                foreach (var tag in tags)
                    model.Tags.Add(new ProductTagModel()
                    {
                        Id = tag.Id,
                        Name = tag.GetLocalized(y => y.Name),
                        SeName = tag.GetSeName(),
                        ProductCount = tag.ProductCount
                    });
                return model;
            });

            return PartialView(cacheModel);
        }

        [NopHttpsRequirement(SslRequirement.No)]
        public ActionResult ProductsByTag(int productTagId, CatalogPagingFilteringModel command)
        {
            return RedirectToRoute("HomePage");
        }

        [NopHttpsRequirement(SslRequirement.No)]
        public ActionResult ProductTagsAll()
        {
            var model = new PopularProductTagsModel();
            model.Tags = _productTagService.GetAllProductTags()
                .OrderByDescending(x => x.ProductCount)
                .Where(x => x.ProductCount > 0)
                //sort by name
                .OrderBy(x => x.GetLocalized(y => y.Name))
                .Select(x =>
                            {
                                var ptModel = new ProductTagModel()
                                {
                                    Id = x.Id,
                                    Name = x.GetLocalized(y => y.Name),
                                    SeName = x.GetSeName(),
                                    ProductCount = x.ProductCount
                                };
                                return ptModel;
                            })
                .ToList();
            return View(model);
        }

        #endregion

        #region Product reviews

        //products reviews
        [NopHttpsRequirement(SslRequirement.No)]
        public ActionResult ProductReviews(int productId)
        {
            var product = _productService.GetProductById(productId);
            if (product == null || product.Deleted || !product.Published || !product.AllowCustomerReviews)
                return RedirectToRoute("HomePage");

            var model = new ProductReviewsModel();
            PrepareProductReviewsModel(model, product);
            //only registered users can leave reviews
            if (_workContext.CurrentCustomer.IsGuest() && !_catalogSettings.AllowAnonymousUsersToReviewProduct)
                ModelState.AddModelError("", _localizationService.GetResource("Reviews.OnlyRegisteredUsersCanWriteReviews"));
            //default value
            model.AddProductReview.Rating = _catalogSettings.DefaultProductRatingValue;
            return View(model);
        }

        [ChildActionOnly]
        public ActionResult ProductPartialReviews(int productId)
        {
            var product = _productService.GetProductById(productId);
            if (product == null || product.Deleted || !product.Published || !product.AllowCustomerReviews)
                return RedirectToRoute("HomePage");

            var model = new ProductReviewsModel();
            PrepareProductReviewsModel(model, product);
            //only registered users can leave reviews
            if (_workContext.CurrentCustomer.IsGuest() && !_catalogSettings.AllowAnonymousUsersToReviewProduct)
                ModelState.AddModelError("", _localizationService.GetResource("Reviews.OnlyRegisteredUsersCanWriteReviews"));
            //default value
            model.AddProductReview.Rating = _catalogSettings.DefaultProductRatingValue;
            return PartialView(model);
        }

        [HttpPost, ActionName("ProductReviews")]
        [FormValueRequired("add-review")]
        [CaptchaValidator]
        public ActionResult ProductReviewsAdd(int productId, ProductReviewsModel model, bool captchaValid)
        {
            var product = _productService.GetProductById(productId);
            if (product == null || product.Deleted || !product.Published || !product.AllowCustomerReviews)
                return RedirectToRoute("HomePage");

            //validate CAPTCHA
            if (_captchaSettings.Enabled && _captchaSettings.ShowOnProductReviewPage && !captchaValid)
            {
                ModelState.AddModelError("", _localizationService.GetResource("Common.WrongCaptcha"));
            }

            if (_workContext.CurrentCustomer.IsGuest() && !_catalogSettings.AllowAnonymousUsersToReviewProduct)
            {
                ModelState.AddModelError("", _localizationService.GetResource("Reviews.OnlyRegisteredUsersCanWriteReviews"));
            }

            if (ModelState.IsValid)
            {
                //save review
                int rating = model.AddProductReview.Rating;
                if (rating < 1 || rating > 5)
                    rating = _catalogSettings.DefaultProductRatingValue;
                bool isApproved = !_catalogSettings.ProductReviewsMustBeApproved;

                var productReview = new ProductReview()
                {
                    ProductId = product.Id,
                    CustomerId = _workContext.CurrentCustomer.Id,
                    IpAddress = _webHelper.GetCurrentIpAddress(),
                    Title = model.AddProductReview.Title,
                    ReviewText = model.AddProductReview.ReviewText,
                    Rating = rating,
                    HelpfulYesTotal = 0,
                    HelpfulNoTotal = 0,
                    IsApproved = isApproved,
                    CreatedOnUtc = DateTime.UtcNow,
                    UpdatedOnUtc = DateTime.UtcNow,
                };
                _customerContentService.InsertCustomerContent(productReview);

                //update product totals
                _productService.UpdateProductReviewTotals(product);

                //update store totals
                _storeService.UpdateProductReviewTotals(productReview.Product.StoreId);

                //notify store owner
                if (_catalogSettings.NotifyStoreOwnerAboutNewProductReviews)
                    _workflowMessageService.SendProductReviewNotificationMessage(productReview, _localizationSettings.DefaultAdminLanguageId);

                //activity log
                _customerActivityService.InsertActivity("PublicStore.AddProductReview", _localizationService.GetResource("ActivityLog.PublicStore.AddProductReview"), product.Name);


                PrepareProductReviewsModel(model, product);
                model.AddProductReview.Title = null;
                model.AddProductReview.ReviewText = null;

                model.AddProductReview.SuccessfullyAdded = true;
                if (!isApproved)
                    model.AddProductReview.Result = _localizationService.GetResource("Reviews.SeeAfterApproving");
                else
                    model.AddProductReview.Result = _localizationService.GetResource("Reviews.SuccessfullyAdded");

                return View(model);
            }

            //If we got this far, something failed, redisplay form
            PrepareProductReviewsModel(model, product);
            return View(model);
        }

        [HttpPost]
        public ActionResult SetProductReviewHelpfulness(int productReviewId, bool washelpful)
        {
            var productReview = _customerContentService.GetCustomerContentById(productReviewId) as ProductReview;
            if (productReview == null)
                throw new ArgumentException("No product review found with the specified id");

            if (_workContext.CurrentCustomer.IsGuest() && !_catalogSettings.AllowAnonymousUsersToReviewProduct)
            {
                return Json(new
                {
                    Result = _localizationService.GetResource("Reviews.Helpfulness.OnlyRegistered"),
                    TotalYes = productReview.HelpfulYesTotal,
                    TotalNo = productReview.HelpfulNoTotal
                });
            }

            //customers aren't allowed to vote for their own reviews
            if (productReview.CustomerId == _workContext.CurrentCustomer.Id)
            {
                return Json(new
                {
                    Result = _localizationService.GetResource("Reviews.Helpfulness.YourOwnReview"),
                    TotalYes = productReview.HelpfulYesTotal,
                    TotalNo = productReview.HelpfulNoTotal
                });
            }

            //delete previous helpfulness
            var oldPrh = (from prh in productReview.ProductReviewHelpfulnessEntries
                          where prh.CustomerId == _workContext.CurrentCustomer.Id
                          select prh).FirstOrDefault();
            if (oldPrh != null)
                _customerContentService.DeleteCustomerContent(oldPrh);

            //insert new helpfulness
            var newPrh = new ProductReviewHelpfulness()
            {
                ProductReviewId = productReview.Id,
                CustomerId = _workContext.CurrentCustomer.Id,
                IpAddress = _webHelper.GetCurrentIpAddress(),
                WasHelpful = washelpful,
                IsApproved = true, //always approved
                CreatedOnUtc = DateTime.UtcNow,
                UpdatedOnUtc = DateTime.UtcNow,
            };
            _customerContentService.InsertCustomerContent(newPrh);

            //new totals
            int helpfulYesTotal = (from prh in productReview.ProductReviewHelpfulnessEntries
                                   where prh.WasHelpful
                                   select prh).Count();
            int helpfulNoTotal = (from prh in productReview.ProductReviewHelpfulnessEntries
                                  where !prh.WasHelpful
                                  select prh).Count();

            productReview.HelpfulYesTotal = helpfulYesTotal;
            productReview.HelpfulNoTotal = helpfulNoTotal;
            _customerContentService.UpdateCustomerContent(productReview);

            return Json(new
            {
                Result = _localizationService.GetResource("Reviews.Helpfulness.SuccessfullyVoted"),
                TotalYes = productReview.HelpfulYesTotal,
                TotalNo = productReview.HelpfulNoTotal
            });
        }

        #endregion

        #region Email a friend

        //products email a friend
        [ChildActionOnly]
        public ActionResult ProductEmailAFriendButton(int productId)
        {
            if (!_catalogSettings.EmailAFriendEnabled)
                return Content("");
            var model = new ProductEmailAFriendModel()
            {
                ProductId = productId
            };

            return PartialView("ProductEmailAFriendButton", model);
        }

        [NopHttpsRequirement(SslRequirement.No)]
        public ActionResult ProductEmailAFriend(int productId)
        {
            var product = _productService.GetProductById(productId);
            if (product == null || product.Deleted || !product.Published || !_catalogSettings.EmailAFriendEnabled)
                return RedirectToRoute("HomePage");

            var model = new ProductEmailAFriendModel();
            model.ProductId = product.Id;
            model.ProductName = product.GetLocalized(x => x.Name);
            model.ProductSeName = product.GetSeName();
            model.YourEmailAddress = _workContext.CurrentCustomer.Email;
            model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnEmailProductToFriendPage;
            return View(model);
        }

        [HttpPost, ActionName("ProductEmailAFriend")]
        [FormValueRequired("send-email")]
        [CaptchaValidator]
        public ActionResult ProductEmailAFriendSend(ProductEmailAFriendModel model, bool captchaValid)
        {
            var product = _productService.GetProductById(model.ProductId);
            if (product == null || product.Deleted || !product.Published || !_catalogSettings.EmailAFriendEnabled)
                return RedirectToRoute("HomePage");

            //validate CAPTCHA
            if (_captchaSettings.Enabled && _captchaSettings.ShowOnEmailProductToFriendPage && !captchaValid)
            {
                ModelState.AddModelError("", _localizationService.GetResource("Common.WrongCaptcha"));
            }

            //check whether the current customer is guest and ia allowed to email a friend
            if (_workContext.CurrentCustomer.IsGuest() && !_catalogSettings.AllowAnonymousUsersToEmailAFriend)
            {
                ModelState.AddModelError("", _localizationService.GetResource("Products.EmailAFriend.OnlyRegisteredUsers"));
            }

            if (ModelState.IsValid)
            {
                //email
                _workflowMessageService.SendProductEmailAFriendMessage(_workContext.CurrentCustomer,
                        _workContext.WorkingLanguage.Id, product,
                        model.YourEmailAddress, model.FriendEmail,
                        Core.Html.HtmlHelper.FormatText(model.PersonalMessage, false, true, false, false, false, false));

                model.ProductId = product.Id;
                model.ProductName = product.GetLocalized(x => x.Name);
                model.ProductSeName = product.GetSeName();

                model.SuccessfullySent = true;
                model.Result = _localizationService.GetResource("Products.EmailAFriend.SuccessfullySent");

                return View(model);
            }

            //If we got this far, something failed, redisplay form
            model.ProductId = product.Id;
            model.ProductName = product.GetLocalized(x => x.Name);
            model.ProductSeName = product.GetSeName();
            model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnEmailProductToFriendPage;
            return View(model);
        }

        #endregion

        #region Comparing products

        //compare products
        public ActionResult AddProductToCompareList(int productId)
        {
            return RedirectToRoute("HomePage");
        }

        public ActionResult RemoveProductFromCompareList(int productId)
        {
            return RedirectToRoute("HomePage");
        }

        [NopHttpsRequirement(SslRequirement.No)]
        public ActionResult CompareProducts()
        {
            return RedirectToRoute("HomePage");
        }

        public ActionResult ClearCompareList()
        {
            return RedirectToRoute("HomePage");
        }

        //[ChildActionOnly]
        //public ActionResult CompareProductsButton(int productId)
        //{
        //    return RedirectToRoute("HomePage");
        //}

        //[ChildActionOnly]
        //public ActionResult CompareListTopCart()
        //{
        //    return RedirectToRoute("HomePage");
        //}

        //[ChildActionOnly]
        //public ActionResult FlyoutCompareList()
        //{
        //    return RedirectToRoute("HomePage");
        //}

        #endregion

        #region Searching

        [NopHttpsRequirement(SslRequirement.No)]
        [ValidateInput(false)]
        public ActionResult Search(SearchModel model, SearchPagingFilteringModel command)
        {
            #region Search criteria

            if (model == null)
                model = new SearchModel();

            model.ShowCategoryFilter = true;
            model.ShowPriceFilter = true;

            if (command.PageSize <= 0)
                command.PageSize = _catalogSettings.SearchPageProductsPerPage;
            if (command.PageNumber <= 0)
                command.PageNumber = 1;
            if (model.Q == null)
                model.Q = "";
            model.Q = model.Q.Trim();

            var orderby = ProductSortingEnum.Position;
            if (Enum.IsDefined(typeof(ProductSortingEnum), model.OrderBy))
                orderby = (ProductSortingEnum)model.OrderBy;

            model.AllowSorting = _catalogSettings.AllowProductSorting;
            if (model.AllowSorting)
            {
                foreach (ProductSortingEnum enumValue in Enum.GetValues(typeof(ProductSortingEnum)))
                {
                    var sortValue = enumValue.GetLocalizedEnum(_localizationService, _workContext);
                    model.AvailableSortmethods.Add(new SelectListItem()
                    {
                        Text = sortValue,
                        Value = ((int)enumValue).ToString(),
                        Selected = enumValue == orderby
                    });
                }
            }

            #endregion

            #region Category filter

            var parentsCategory = new List<SelectListItem>();
            var allCategories = _categoryService.GetAllCategories();
            model.CategoryName = null;
            // Dictionary<int, string> parentsCategory = new Dictionary<int, string>();
            if (model.Cid > 0)
            {
                if (String.IsNullOrEmpty(model.Q))
                {
                    var currentCategory = allCategories.FirstOrDefault(c => c.Id == model.Cid);
                    if (currentCategory != null)
                        model.CategoryName = currentCategory.GetLocalized(c => c.Name);
                }

                var cat = allCategories.FirstOrDefault(c => c.Id == model.Cid);
                while (cat != null)
                {
                    parentsCategory.Add(new SelectListItem()
                    {
                        Value = cat.Id.ToString(),
                        Text = cat.GetLocalized(c => c.Name)
                    });
                    cat = allCategories.FirstOrDefault(c => c.Id == cat.ParentCategoryId);
                }
                // Reverse order since parents are added towards root
                if (parentsCategory.Count > 0)
                    parentsCategory.Reverse();
            }

            var subCategories = new List<SelectListItem>();
            var isBrowse = !(model.Cid > 0) && String.IsNullOrEmpty(model.Q);
            if (isBrowse)
            {
                subCategories = new List<SelectListItem>(
                    allCategories
                        .Where(c => !(c.ParentCategoryId > 0))
                        .Select(c => new SelectListItem() { Value = c.Id.ToString(), Text = c.GetLocalized(x => x.Name) })
                    );
            }
            else
            {
                subCategories = new List<SelectListItem>(
                    allCategories
                        .Where(c => c.ParentCategoryId == model.Cid)
                        .Select(c => new SelectListItem() { Value = c.Id.ToString(), Text = c.GetLocalized(x => x.Name) })
                    );
            }
            #endregion

            #region Search

            IPagedList<Product> products = new PagedList<Product>(new List<Product>(), 0, 1);
            // only search if query string search keyword is set (used to avoid searching or displaying search term min length error message on /search page load)
            if (model.Q != null || (model.Store != null && model.Store.Id > 1) || model.Cid > 0)
            {
                if (model.Q.Length < _catalogSettings.ProductSearchTermMinimumLength && model.Q.Length > 0 && ((model.Store == null || model.Store.Id <= 1) && model.Cid == 0))
                {
                    model.Warning = string.Format(_localizationService.GetResource("Search.SearchTermMinimumLengthIsNCharacters"), _catalogSettings.ProductSearchTermMinimumLength);
                }
                else
                {
                    var categoryIds = new List<int>();
                    int manufacturerId = model.Mid;
                    int storeId = 1;
                    decimal? minPriceConverted = null;
                    decimal? maxPriceConverted = null;
                    bool searchInDescriptions = true;
                    bool searchInProductTags = true;

                    //search in categories
                    var categoryId = model.Cid;
                    if (categoryId > 0)
                    {
                        categoryIds.Add(categoryId);
                        {
                            categoryIds.AddRange(allCategories.Where(a => a.ParentCategoryId == categoryId).Select(a => a.Id));
                        }
                    }

                    //min price
                    if (!string.IsNullOrEmpty(model.Pf))
                    {
                        decimal minPrice = decimal.Zero;
                        if (decimal.TryParse(model.Pf, out minPrice))
                            minPriceConverted = _currencyService.ConvertToPrimaryStoreCurrency(minPrice, _workContext.WorkingCurrency);
                    }
                    //max price
                    if (!string.IsNullOrEmpty(model.Pt))
                    {
                        decimal maxPrice = decimal.Zero;
                        if (decimal.TryParse(model.Pt, out maxPrice))
                            maxPriceConverted = _currencyService.ConvertToPrimaryStoreCurrency(maxPrice, _workContext.WorkingCurrency);
                    }

                    decimal? outMinPrice = decimal.MinValue;
                    decimal? outMaxPrice = decimal.MaxValue;
                    //products
                    IList<int> filterableSpecificationAttributeOptionIds = null;
                    products = _productService.SearchProducts(GetRegionId(), null, categoryIds, manufacturerId, null,
                        minPriceConverted, maxPriceConverted, 0,
                        model.Q, searchInDescriptions, searchInProductTags, _workContext.WorkingLanguage.Id, null,
                        (ProductSortingEnum)orderby, command.PageNumber - 1, command.PageSize,
                        false, out filterableSpecificationAttributeOptionIds, ref outMinPrice, ref outMaxPrice, storeId: storeId);

                    if (products.TotalCount == 0 && model.MaxPrice == null && model.MinPrice == null)
                    {
                        model.ShowPriceFilter = false;
                    }

                    if (model.Sc == 0 && model.Cid == 0 && products.TotalCount == 0)
                    {
                        model.ShowCategoryFilter = false;
                    }

                    model.Products = PrepareProductOverviewModels(products).ToList();

                    decimal minimumPrice = outMinPrice.Value;
                    decimal maximumPrice = outMaxPrice.Value;

                    var productCategories = new List<ProductCategory>();
                    foreach (var product in products)
                    {
                        //var variants = product.GetProductVariants(GetRegionId()).ToList();
                        //if (model.ShowPriceFilter && variants.Any())
                        //{
                        //    decimal? price;
                        //    List<TaxSurcharge> tr;
                        //    var pv = _priceCalculationService.GetProductVariantWithMinimalPrice(variants, _workContext.CurrentCustomer, true, int.MaxValue, out price);
                        //    decimal finalPrice = _taxService.GetProductPrice(pv, price.Value, out tr);

                        //    if (finalPrice >= 0 && minimumPrice > finalPrice)
                        //        minimumPrice = finalPrice;
                        //    if (finalPrice > 0 && maximumPrice < finalPrice)
                        //        maximumPrice = finalPrice;
                        //}
                        if (!isBrowse && model.ShowCategoryFilter && product.ProductCategories.Count > 0)
                        {
                            productCategories.AddRange(product.ProductCategories.Except(productCategories));
                        }
                    }
                    if (productCategories.Count > 0)
                    {
                        subCategories = subCategories.Where(subCategory => productCategories.Any(pc => pc.CategoryId.ToString() == subCategory.Value)).ToList();
                    }


                    if (model.ShowPriceFilter)
                    {
                        minimumPrice = _currencyService.ConvertFromPrimaryStoreCurrency(minimumPrice, _workContext.WorkingCurrency);
                        maximumPrice = _currencyService.ConvertFromPrimaryStoreCurrency(maximumPrice, _workContext.WorkingCurrency);

                        if (model.MinPrice == null)
                            model.MinPrice = MathEx.Round(minimumPrice);
                        if (model.MaxPrice == null)
                            model.MaxPrice = MathEx.Round(maximumPrice);
                    }

                    if (model.ShowCategoryFilter)
                    {
                        model.ParentsCategory = parentsCategory;
                        model.AvailableCategories = subCategories;
                    }

                    model.NoResults = !model.Products.Any();

                    // Featured product
                    if (!model.NoResults && model.Cid > 0 && String.IsNullOrEmpty(model.Q))
                    {
                        var featured = _productService.SearchProducts(GetRegionId(), null, categoryIds, manufacturerId, null,
                        null, null, 0, String.Empty, false, false, _workContext.WorkingLanguage.Id, null,
                        (ProductSortingEnum)orderby, 0, 100,
                        false, out filterableSpecificationAttributeOptionIds,false,storeId,false,true);
                        if (featured != null && featured.Count() > 0)
                        {
                            model.FeaturedProduct = PrepareProductOverviewModels(featured).FirstOrDefault();
                        }
                        else
                        {
                            model.FeaturedProduct = model.Products.FirstOrDefault();
                        }
                    }

                    //event
                    _eventPublisher.Publish(new ProductSearchEvent()
                    {
                        SearchTerm = model.Q,
                        SearchInDescriptions = searchInDescriptions,
                        CategoryIds = categoryIds,
                        ManufacturerId = manufacturerId,
                        WorkingLanguageId = _workContext.WorkingLanguage.Id
                    });
                }
            }

            #endregion

            model.PagingFilteringContext.LoadPagedList(products);


            return View(model);
        }

        [ChildActionOnly]
        public ActionResult SearchBox(CatalogPagingFilteringModel command, int? Cid)
        {
            var model = new SearchBoxFrontModel()
            {
                AutoCompleteEnabled = _catalogSettings.ProductSearchAutoCompleteEnabled,
                ShowProductImagesInSearchAutoComplete = _catalogSettings.ShowProductImagesInSearchAutoComplete,
                SearchTermMinimumLength = _catalogSettings.ProductSearchTermMinimumLength
            };
            var categories = _categoryService.GetAllCategories();
            if (categories.Count > 0)
            {
                //first empty entry
                model.AvailableCategories.Add(new SelectListItem()
                {
                    Value = "0",
                    Text = _localizationService.GetResource("Common.All")
                });
                //all other categories
                foreach (var c in categories)
                {
                    //generate full category name (breadcrumb)
                    string fullCategoryBreadcrumbName = "";
                    var breadcrumb = GetCategoryBreadCrumb(c);
                    for (int i = 0; i <= breadcrumb.Count - 1; i++)
                    {
                        if (i == breadcrumb.Count - 1)
                        {
                            fullCategoryBreadcrumbName += breadcrumb[i].GetLocalized(x => x.Name);
                        }
                        //fullCategoryBreadcrumbName += breadcrumb[i].GetLocalized(x => x.Name);
                        //if (i != breadcrumb.Count - 1)
                        //fullCategoryBreadcrumbName += " >> ";
                    }

                    model.AvailableCategories.Add(new SelectListItem()
                    {
                        Value = c.Id.ToString(),
                        Text = fullCategoryBreadcrumbName,
                        Selected = Cid == c.Id
                    });
                }
            }
            if (Cid != null)
            {
                model.Cid = (int)Cid;
            }
            model.PagingFilteringContext.AllowProductSorting = _catalogSettings.AllowProductSorting;
            if (model.PagingFilteringContext.AllowProductSorting)
            {
                foreach (ProductSortingEnum enumValue in Enum.GetValues(typeof(ProductSortingEnum)))
                {
                    var currentPageUrl = _webHelper.GetThisPageUrl(true);
                    var sortUrl = _webHelper.ModifyQueryString(currentPageUrl, "orderby=" + ((int)enumValue).ToString(), null);

                    var sortValue = enumValue.GetLocalizedEnum(_localizationService, _workContext);
                    model.PagingFilteringContext.AvailableSortOptions.Add(new SelectListItem()
                    {
                        Text = sortValue,
                        Value = sortUrl,
                        Selected = enumValue == (ProductSortingEnum)command.OrderBy
                    });
                }
            }

            var regions = _regionService.GetAllRegions();
            model.AvailableRegions = new List<SelectListItem>();
            model.CurrentRegionId = _workContext.CurrentCustomer.Region != null ? _workContext.CurrentCustomer.Region.Id : 0;
            foreach (var region in regions)
            {
                model.AvailableRegions.Add(new SelectListItem()
                {
                    Selected = region.Id == model.CurrentRegionId,
                    Text = region.Name,
                    Value = region.Id.ToString()
                });
            }



            #region user Default regions
            var firstOrDefault = model.AvailableRegions.FirstOrDefault(ar => ar.Value == model.CurrentRegionId.ToString());
            if (firstOrDefault != null)
            {
                model.UserDefaultRegions = firstOrDefault.Text;
            }
            else
            {
                var selectListItem = regions.OrderBy(i => i.DisplayOrder).FirstOrDefault();
                if (selectListItem != null)
                    model.UserDefaultRegions = selectListItem.Name;
            }

            #endregion

            return PartialView(model);
        }

        public ActionResult SearchTermAutoComplete(string term)
        {


            if (String.IsNullOrWhiteSpace(term) || term.Length < _catalogSettings.ProductSearchTermMinimumLength)
                return Content("");

            //products
            var productNumber = _catalogSettings.ProductSearchAutoCompleteNumberOfProducts > 0 ?
                _catalogSettings.ProductSearchAutoCompleteNumberOfProducts : 10;
            IList<int> filterableSpecificationAttributeOptionIds = null;
            var products = _productService.SearchProducts(GetRegionId(), null, null, 0, null,
                null, null, 0,
                term, false, false, _workContext.WorkingLanguage.Id, null,
                ProductSortingEnum.Position, 0, productNumber,
                false, out filterableSpecificationAttributeOptionIds, searchStoreNames: true);
            // TODO: Pending on the design should show stores or not?
            var models = PrepareProductOverviewModels(products, false, /*_catalogSettings.ShowProductImagesInSearchAutoComplete*/true, false, false, _mediaSettings.AutoCompleteSearchThumbPictureSize).ToList();
            var result = (from p in models
                          select new
                          {
                              label = p.Name,
                              producturl = Url.RouteUrl("Product", new { SeName = p.SeName }),
                              productpictureurl = p.DefaultPictureModel.ImageUrl
                          })
                          .ToList();
            return Json(result, JsonRequestBehavior.AllowGet);
        }

        #endregion

        #region Reserve
        public ActionResult ReserveSummary(bool? prepareAndDisplayOrderReviewData, int? stoRreId)
        {
            return PartialView();
        }
        public ActionResult OrderTotals(bool isEditable, int? storeIdz)
        {
            //var query = _workContext.CurrentCustomer.ShoppingCartItems.Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart);

            //var cart = query.ToList();
            ReserveCarModel model = new ReserveCarModel();
            if (Session["ReserveCar"] != null)
                model = (ReserveCarModel)Session["ReserveCar"];
            return PartialView(PrepareOrderTotalsModel(model));
        }
        public OrderTotalsModel PrepareOrderTotalsModel(ReserveCarModel reserveCar)
        {
            if (reserveCar.ProductVariantId == 0)
                return new OrderTotalsModel();
            var model = new OrderTotalsModel();
            model.IsEditable = false;
            var shoppingCarItems = ReserveCarModel.ConvertToShoppingCarItem(reserveCar, _productService, _workContext, _storeService);
            string paymentMethodSystemName = _workContext.CurrentCustomer != null ? _workContext.CurrentCustomer.SelectedPaymentMethodSystemName : null;
            #region subtotal
            //subtotal
            decimal subtotalBase = decimal.Zero;
            decimal orderSubTotalDiscountAmountBase = decimal.Zero;
            Discount orderSubTotalAppliedDiscount = null;
            decimal subTotalWithoutDiscountBase = decimal.Zero;
            decimal subTotalWithDiscountBase = decimal.Zero;
            _orderTotalCalculationService.GetShoppingCartSubTotal(shoppingCarItems,
                out orderSubTotalDiscountAmountBase, out orderSubTotalAppliedDiscount,
                out subTotalWithoutDiscountBase, out subTotalWithDiscountBase);
            subtotalBase = subTotalWithoutDiscountBase;
            decimal subtotal = _currencyService.ConvertFromPrimaryStoreCurrency(subtotalBase, _workContext.WorkingCurrency);
            model.SubTotal = _priceFormatter.FormatPrice(subtotal);
            if (orderSubTotalDiscountAmountBase > decimal.Zero)
            {
                decimal orderSubTotalDiscountAmount = _currencyService.ConvertFromPrimaryStoreCurrency(orderSubTotalDiscountAmountBase, _workContext.WorkingCurrency);
                model.SubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountAmount);
                model.AllowRemovingSubTotalDiscount = orderSubTotalAppliedDiscount != null &&
                    orderSubTotalAppliedDiscount.RequiresCouponCode &&
                    !String.IsNullOrEmpty(orderSubTotalAppliedDiscount.CouponCode) &&
                    model.IsEditable;
            }
            //shipping info
            model.RequiresShipping = shoppingCarItems.RequiresShipping();
            if (model.RequiresShipping)
            {
                decimal? shoppingCartShippingBase = _orderTotalCalculationService.GetShoppingCartShippingTotal(shoppingCarItems);
                if (shoppingCartShippingBase.HasValue)
                {
                    decimal shoppingCartShipping = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartShippingBase.Value, _workContext.WorkingCurrency);
                    model.Shipping = _priceFormatter.FormatShippingPrice(shoppingCartShipping, true);

                    //selected shipping method
                    var shippingOption = _workContext.CurrentCustomer.GetAttribute<ShippingOption>(SystemCustomerAttributeNames.LastShippingOption);
                    if (shippingOption != null)
                        model.SelectedShippingMethod = shippingOption.Name;
                }
            }

            //payment method fee
            decimal paymentMethodAdditionalFee = _paymentService.GetAdditionalHandlingFee(shoppingCarItems, paymentMethodSystemName);
            decimal paymentMethodAdditionalFeeWithTaxBase = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, _workContext.CurrentCustomer);
            if (paymentMethodAdditionalFeeWithTaxBase > decimal.Zero)
            {
                decimal paymentMethodAdditionalFeeWithTax = _currencyService.ConvertFromPrimaryStoreCurrency(paymentMethodAdditionalFeeWithTaxBase, _workContext.WorkingCurrency);
                model.PaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeWithTax, true);
            }

            //tax
            bool displayTax = true;
            bool displayTaxRates = true;
            if (_taxSettings.HideTaxInOrderSummary && _workContext.TaxDisplayType == TaxDisplayType.IncludingTax)
            {
                displayTax = false;
                displayTaxRates = false;
            }
            else
            {
                List<TaxSurcharge> taxRates;
                decimal shoppingCartTaxBase = _orderTotalCalculationService.GetTaxTotal(shoppingCarItems, out taxRates);
                decimal shoppingCartTax = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartTaxBase, _workContext.WorkingCurrency);

                if (shoppingCartTaxBase == 0 && _taxSettings.HideZeroTax)
                {
                    displayTax = false;
                    displayTaxRates = false;
                }
                else
                {
                    displayTaxRates = _taxSettings.DisplayTaxRates && taxRates.Count > 0;
                    displayTax = !displayTaxRates;

                    model.Tax = _priceFormatter.FormatPrice(shoppingCartTax, true, false);
                    foreach (var tr in taxRates)
                    {
                        model.TaxRates.Add(new TaxRateModel()
                        {
                            NameRate = _priceFormatter.FormatTaxRate(tr, _taxSettings.DisplayTaxRatePercentage && !tr.IsFixedCharge),
                            Value = _priceFormatter.FormatPrice(_currencyService.ConvertFromPrimaryStoreCurrency(tr.Value, _workContext.WorkingCurrency), true, false),
                        });
                    }
                }
            }
            model.DisplayTaxRates = displayTaxRates;
            model.DisplayTax = displayTax;

            //total
            decimal orderTotalDiscountAmountBase = decimal.Zero;
            Discount orderTotalAppliedDiscount = null;
            List<AppliedGiftCard> appliedGiftCards = null;
            int redeemedRewardPoints = 0;
            decimal redeemedRewardPointsAmount = decimal.Zero;
            List<TaxSurcharge> taxRate;
            decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(shoppingCarItems,
                out orderTotalDiscountAmountBase, out orderTotalAppliedDiscount,
                out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, out taxRate);
            if (shoppingCartTotalBase.HasValue)
            {
                decimal shoppingCartTotal = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartTotalBase.Value, _workContext.WorkingCurrency);
                model.OrderTotal = _priceFormatter.FormatPrice(shoppingCartTotal, true, false);
            }

            //discount
            if (orderTotalDiscountAmountBase > decimal.Zero)
            {
                decimal orderTotalDiscountAmount = _currencyService.ConvertFromPrimaryStoreCurrency(orderTotalDiscountAmountBase, _workContext.WorkingCurrency);
                model.OrderTotalDiscount = _priceFormatter.FormatPrice(-orderTotalDiscountAmount, true, false);
                model.AllowRemovingOrderTotalDiscount = orderTotalAppliedDiscount != null &&
                    orderTotalAppliedDiscount.RequiresCouponCode &&
                    !String.IsNullOrEmpty(orderTotalAppliedDiscount.CouponCode) &&
                    model.IsEditable;
            }

            //gift cards
            if (appliedGiftCards != null && appliedGiftCards.Count > 0)
            {
                foreach (var appliedGiftCard in appliedGiftCards)
                {
                    var gcModel = new OrderTotalsModel.GiftCard()
                    {
                        Id = appliedGiftCard.GiftCard.Id,
                        CouponCode = appliedGiftCard.GiftCard.GiftCardCouponCode,
                    };
                    decimal amountCanBeUsed = _currencyService.ConvertFromPrimaryStoreCurrency(appliedGiftCard.AmountCanBeUsed, _workContext.WorkingCurrency);
                    gcModel.Amount = _priceFormatter.FormatPrice(-amountCanBeUsed, true, false);

                    decimal remainingAmountBase = appliedGiftCard.GiftCard.GetGiftCardRemainingAmount() - appliedGiftCard.AmountCanBeUsed;
                    decimal remainingAmount = _currencyService.ConvertFromPrimaryStoreCurrency(remainingAmountBase, _workContext.WorkingCurrency);
                    gcModel.Remaining = _priceFormatter.FormatPrice(remainingAmount, true, false);

                    model.GiftCards.Add(gcModel);
                }
            }

            //reward points
            if (redeemedRewardPointsAmount > decimal.Zero)
            {
                decimal redeemedRewardPointsAmountInCustomerCurrency = _currencyService.ConvertFromPrimaryStoreCurrency(redeemedRewardPointsAmount, _workContext.WorkingCurrency);
                model.RedeemedRewardPoints = redeemedRewardPoints;
                model.RedeemedRewardPointsAmount = _priceFormatter.FormatPrice(-redeemedRewardPointsAmountInCustomerCurrency, true, false);
            }
            #endregion
            return model;
        }
        #endregion

        #region Stores

#if false

        public ActionResult Store(int storeId, CatalogPagingFilteringModel command)
        {
            return RedirectToRoute("HomePage");
            var store = _storeService.GetStoreById(storeId);
            if (store == null || store.Deleted || !store.Published)
                return RedirectToRoute("HomePage");

            //'Continue shopping' URL	
            _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.LastContinueShoppingPage, _webHelper.GetThisPageUrl(false));

            if (command.PageNumber <= 0) command.PageNumber = 1;

            var model = store.ToModel();

            //sorting	
            model.PagingFilteringContext.AllowProductSorting = _catalogSettings.AllowProductSorting;
            if (model.PagingFilteringContext.AllowProductSorting)
            {
                foreach (ProductSortingEnum enumValue in Enum.GetValues(typeof(ProductSortingEnum)))
                {
                    var currentPageUrl = _webHelper.GetThisPageUrl(true);
                    var sortUrl = _webHelper.ModifyQueryString(currentPageUrl, "orderby=" + ((int)enumValue).ToString(), null);

                    var sortValue = enumValue.GetLocalizedEnum(_localizationService, _workContext);
                    model.PagingFilteringContext.AvailableSortOptions.Add(new SelectListItem()
                    {
                        Text = sortValue,
                        Value = sortUrl,
                        Selected = enumValue == (ProductSortingEnum)command.OrderBy
                    });
                }
            }


            //view mode	
            model.PagingFilteringContext.AllowProductViewModeChanging = _catalogSettings.AllowProductViewModeChanging;
            var viewMode = !string.IsNullOrEmpty(command.ViewMode)
                ? command.ViewMode
                : _catalogSettings.DefaultViewMode;
            if (model.PagingFilteringContext.AllowProductViewModeChanging)
            {
                var currentPageUrl = _webHelper.GetThisPageUrl(true);
                //grid	
                model.PagingFilteringContext.AvailableViewModes.Add(new SelectListItem()
                {
                    Text = _localizationService.GetResource("Stores.ViewMode.Grid"),
                    Value = _webHelper.ModifyQueryString(currentPageUrl, "viewmode=grid", null),
                    Selected = viewMode == "grid"
                });
                //list
                model.PagingFilteringContext.AvailableViewModes.Add(new SelectListItem()
                {
                    Text = _localizationService.GetResource("Stores.ViewMode.List"),
                    Value = _webHelper.ModifyQueryString(currentPageUrl, "viewmode=list", null),
                    Selected = viewMode == "list"
                });
            }

            //page size	
            model.PagingFilteringContext.AllowCustomersToSelectPageSize = false;
            if (store.AllowCustomersToSelectPageSize && store.PageSizeOptions != null)
            {
                var pageSizes = store.PageSizeOptions.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);

                if (pageSizes.Any())
                {
                    // get the first page size entry to use as the default (store page load) or if customer enters invalid value via query string	
                    if (command.PageSize <= 0 || !pageSizes.Contains(command.PageSize.ToString()))
                    {
                        int temp = 0;

                        if (int.TryParse(pageSizes.FirstOrDefault(), out temp))
                        {
                            if (temp > 0)
                            {
                                command.PageSize = temp;
                            }
                        }
                    }

                    var currentPageUrl = _webHelper.GetThisPageUrl(true);
                    var sortUrl = _webHelper.ModifyQueryString(currentPageUrl, "pagesize={0}", null);
                    sortUrl = _webHelper.RemoveQueryString(sortUrl, "pagenumber");


                    foreach (var pageSize in pageSizes)
                    {
                        int temp = 0;
                        if (!int.TryParse(pageSize, out temp))
                        {
                            continue;
                        }
                        if (temp <= 0)
                        {
                            continue;
                        }

                        model.PagingFilteringContext.PageSizeOptions.Add(new SelectListItem()
                        {
                            Text = pageSize,
                            Value = String.Format(sortUrl, pageSize),
                            Selected = pageSize.Equals(command.PageSize.ToString(), StringComparison.InvariantCultureIgnoreCase)
                        });
                    }

                    model.PagingFilteringContext.PageSizeOptions = model.PagingFilteringContext.PageSizeOptions.OrderBy(x => int.Parse(x.Text)).ToList();

                    if (model.PagingFilteringContext.PageSizeOptions.Any())
                    {
                        model.PagingFilteringContext.PageSizeOptions = model.PagingFilteringContext.PageSizeOptions.OrderBy(x => int.Parse(x.Text)).ToList();
                        model.PagingFilteringContext.AllowCustomersToSelectPageSize = true;

                        if (command.PageSize <= 0)
                        {
                            command.PageSize = int.Parse(model.PagingFilteringContext.PageSizeOptions.FirstOrDefault().Text);
                        }
                    }
                }
            }

            if (command.PageSize <= 0) command.PageSize = store.PageSize;


            //price ranges	
            model.PagingFilteringContext.PriceRangeFilter.LoadPriceRangeFilters(store.PriceRanges, _webHelper, _priceFormatter);
            var selectedPriceRange = model.PagingFilteringContext.PriceRangeFilter.GetSelectedPriceRange(_webHelper, store.PriceRanges);
            decimal? minPriceConverted = null;
            decimal? maxPriceConverted = null;
            if (selectedPriceRange != null)
            {
                if (selectedPriceRange.From.HasValue)
                    minPriceConverted = _currencyService.ConvertToPrimaryStoreCurrency(selectedPriceRange.From.Value, _workContext.WorkingCurrency);

                if (selectedPriceRange.To.HasValue)
                    maxPriceConverted = _currencyService.ConvertToPrimaryStoreCurrency(selectedPriceRange.To.Value, _workContext.WorkingCurrency);
            }


            //products	
            IList<int> filterableSpecificationAttributeOptionIds = null;
            var products = _productService.SearchProducts(_workContext.CurrentCustomer.Region.Id, 0,
                _catalogSettings.IncludeFeaturedProductsInNormalLists ? null : (bool?)false,
                minPriceConverted, maxPriceConverted,
                0, string.Empty, false, false, _workContext.WorkingLanguage.Id, null,
                (ProductSortingEnum)command.OrderBy, command.PageNumber - 1, command.PageSize,
                false, out filterableSpecificationAttributeOptionIds, false, store.Id
                );
            model.Products = PrepareProductOverviewModels(products).ToList();

            model.PagingFilteringContext.LoadPagedList(products);
            model.PagingFilteringContext.ViewMode = command.ViewMode;

            ////Branches	
            var branches = store.Branches.OrderBy(b => b.DisplayOrder).ToList();
            model.Branches = branches.Select(x => x.ToModel()).ToList();

            //Picture	
            model.PictureModel.ImageUrl = _pictureService.GetPictureUrl(store.PictureId, _mediaSettings.ManufacturerThumbPictureSize, true); // use manufacturer picture size	
            model.PictureModel.Title = string.Format(_localizationService.GetResource("Media.Store.ImageLinkTitleFormat"), model.Name);
            model.PictureModel.AlternateText = string.Format(_localizationService.GetResource("Media.Store.ImageAlternateTextFormat"), model.Name);


            //template	
            var template = _storeTemplateService.GetStoreTemplateById(store.StoreTemplateId);
            if (template == null)
                template = _storeTemplateService.GetAllStoreTemplates().FirstOrDefault();
            model.StoreLink = _webHelper.GetThisPageUrl(true);
            return View(template.ViewPath, model);
        }

        [ChildActionOnly]
        public ActionResult HomepageStores(int categoryId = 0)
        {
            return Content("");
            var model = new List<StoreModel>();

            var stores = _storeService.GetFeaturedStores(categoryId);

            if (stores.Count == 0)
                return Content("");

            foreach (var store in stores)
            {
                var storePictureCacheKey = string.Format(ModelCacheEventConsumer.STORE_PICTURE_MODEL_KEY, store.Id, _mediaSettings.StoreLogoFeaturedStorePictureSize, true, _workContext.WorkingLanguage.Id, _webHelper.IsCurrentConnectionSecured());
                var modelSto = store.ToModel();
                modelSto.PictureModel = _cacheManager.Get(storePictureCacheKey, () =>
                {
                    var pictureModel = new PictureModel()
                    {
                        ImageUrl = _pictureService.GetPictureUrl(store.PictureId, _mediaSettings.StoreLogoProductBoxPictureSize),
                        FullSizeImageUrl = _pictureService.GetPictureUrl(store.PictureId),
                        Title = string.Format(_localizationService.GetResource("Media.Product.ImageLinkTitleFormat"), modelSto.Name),
                        AlternateText = string.Format(_localizationService.GetResource("Media.Product.ImageAlternateTextFormat"), modelSto.Name)
                    };
                    return pictureModel;
                });
                model.Add(modelSto);
            }

            return View(model);
        }

        public ActionResult StoreAll()
        {
            return RedirectToRoute("HomePage");
            var model = new List<StoreModel>();
            var stores = _storeService.GetAllStores();
            foreach (var store in stores)
            {
                var modelSto = store.ToModel();
                modelSto.PictureModel.ImageUrl = _pictureService.GetPictureUrl(store.PictureId, _mediaSettings.StoreLogoFeaturedStorePictureSize, true); // use manufacturer picture size	
                modelSto.PictureModel.Title = string.Format(_localizationService.GetResource("Media.Store.ImageLinkTitleFormat"), modelSto.Name);
                modelSto.PictureModel.AlternateText = string.Format(_localizationService.GetResource("Media.Store.ImageAlternateTextFormat"), modelSto.Name);
                model.Add(modelSto);
            }

            return View(model);
        }

        [ChildActionOnly]
        //[OutputCache(Duration = 120, VaryByCustom = "WorkingLanguage")]	
        public ActionResult StoreNavigation(int currentStoreId)
        {
            var currentStore = _storeService.GetStoreById(currentStoreId);

            var model = new List<ProductOverviewModel.ProductStoreModel>();
            var allStores = _storeService.GetAllStores();
            foreach (var store in allStores)
            {
                var modelSto = new ProductOverviewModel.ProductStoreModel()
                {
                    Id = store.Id,
                    Name = store.GetLocalized(x => x.Name),
                    SeName = store.GetSeName(),
                    IsActive = currentStore != null && currentStore.Id == store.Id,
                };
                model.Add(modelSto);
            }

            return PartialView(model);
        }

        public ActionResult StoreNavigationFront(int currentStoreId)
        {
            var currentStore = _storeService.GetStoreById(currentStoreId);

            var model = new List<ProductOverviewModel.ProductStoreModel>();
            var allStores = _storeService.GetAllStores();
            foreach (var store in allStores)
            {
                var modelSto = new ProductOverviewModel.ProductStoreModel()
                {
                    Id = store.Id,
                    Name = store.GetLocalized(x => x.Name),
                    SeName = store.GetSeName(),
                    IsActive = currentStore != null && currentStore.Id == store.Id,
                };

                model.Add(modelSto);
            }

            return PartialView(model);
        }

        [ChildActionOnly]
        public ActionResult StoreLogo(int currentStoreId)
        {
            var store = _storeService.GetStoreById(currentStoreId);
            if (store == null)
                return Content("");

            var model = store.ToModel();
            model.PictureModel.ImageUrl = _pictureService.GetPictureUrl(store.PictureId, _mediaSettings.StoreLogoStorePagePictureSize, true);
            model.PictureModel.Title = string.Format(_localizationService.GetResource("Media.Store.ImageLinkTitleFormat"), model.Name);
            model.PictureModel.AlternateText = string.Format(_localizationService.GetResource("Media.Store.ImageAlternateTextFormat"), model.Name);

            return PartialView(model);
        }

        [ChildActionOnly]
        public ActionResult StoreRating(int currentStoreId)
        {
            var store = _storeService.GetStoreById(currentStoreId);
            if (store == null)
                return Content("");

            var model = new StoreReviewOverviewModel()
            {
                StoreId = currentStoreId,
                RatingSum = store.ApprovedRatingSum,
                TotalReviews = store.ApprovedTotalReviews,
            };

            return PartialView(model);
        }

        [ChildActionOnly]
        public ActionResult StoreLocationFrontPageInfo(int currentStoreId, int branchId = 0)
        {
            var branch = _storeService.GetStoreBranchById(currentStoreId, branchId);
            if (branch == null)
                return Content("");

            var model = new BranchModel();
            model = branch.ToModel();

            model.StoreFrontPictureModel = new PictureModel()
            {
                ImageUrl = _pictureService.GetPictureUrl(model.StoreFrontPictureId, _mediaSettings.StorePictureSize, true),
                FullSizeImageUrl = _pictureService.GetPictureUrl(model.StoreFrontPictureId, showDefaultPicture: true),
                Title = string.Format(_localizationService.GetResource("Media.Store.ImageLinkTitleFormat"), model.Name),
                AlternateText = string.Format(_localizationService.GetResource("Media.Store.ImageAlternateTextFormat"), model.Name)
            };

            if (!String.IsNullOrEmpty(_logisticsInformationSettings.BingMapKey))
            {
                model.MapApiKey = _logisticsInformationSettings.BingMapKey;
            }

            return PartialView(model);
        }

        [HttpPost]
        public ActionResult AddStoreToFavorite(int storeId)
        {
            var customer = _workContext.CurrentCustomer;
            try
            {
                _storeService.AddFavoriteStore(storeId, customer);
            }
            catch (Exception exc)
            {
                return Json(new
                {
                    success = false,
                    message = exc.Message
                });
            }

            var updatetopcartsectionhtml = _storeService.GetFavoriteStores(customer).Count.ToString();
            var updateflyoutcartsectionhtml = this.RenderPartialViewToString("FlyoutFavoriteStore", PrepareFavoriteStoreModel(customer));

            return Json(new
            {
                success = true,
                message = string.Format(_localizationService.GetResource("Store.StoreHasBeenAddedToFavorite")),
                updatetopstoresectionhtml = updatetopcartsectionhtml,
                updateflyoutstoresectionhtml = updateflyoutcartsectionhtml,
            });
        }

        [HttpPost]
        public ActionResult RemoveStoreFromFavorite(int storeId)
        {
            var customer = _workContext.CurrentCustomer;
            try
            {
                _storeService.RemoveFavoriteStore(storeId, customer);
            }
            catch (Exception exc)
            {
                return Json(new
                {
                    success = false,
                    message = exc.Message
                });
            }

            var updatetopcartsectionhtml = _storeService.GetFavoriteStores(customer).Count.ToString();
            var updateflyoutcartsectionhtml = this.RenderPartialViewToString("FlyoutFavoriteStore", PrepareFavoriteStoreModel(customer));

            return Json(new
            {
                success = true,
                message = string.Format(_localizationService.GetResource("Store.StoreHasBeenRemovedFromFavorite")),
                updatetopstoresectionhtml = updatetopcartsectionhtml,
                updateflyoutstoresectionhtml = updateflyoutcartsectionhtml,
            });
        }

        [HttpPost]
        public ActionResult RemoveAllStoresFromFavorite()
        {
            var customer = _workContext.CurrentCustomer;
            var stores = _storeService.GetFavoriteStores(customer);
            try
            {
                foreach (var store in stores)
                {
                    int storeId = store.Id;
                    _storeService.RemoveFavoriteStore(storeId, customer);
                }
            }
            catch (Exception exc)
            {
                return Json(new
                {
                    success = false,
                    message = exc.Message
                });
            }

            var updatetopcartsectionhtml = _storeService.GetFavoriteStores(customer).Count.ToString();
            var updateflyoutcartsectionhtml = this.RenderPartialViewToString("FlyoutFavoriteStore", PrepareFavoriteStoreModel(customer));

            return Json(new
            {
                success = true,
                message = string.Format(_localizationService.GetResource("Store.StoreHasBeenRemovedFromFavorite")),
                updatetopstoresectionhtml = updatetopcartsectionhtml,
                updateflyoutstoresectionhtml = updateflyoutcartsectionhtml,
            });
        }

        [ChildActionOnly]
        public ActionResult FlyoutFavoriteStore()
        {
            var model = PrepareFavoriteStoreModel(_workContext.CurrentCustomer);
            return PartialView(model);
        }

#endif

        #endregion
    }
}
